The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа
Вперед Назад Содержание

4. Расширения Семейства Языка C

GNU C обеспечивает некоторые языковые свойства, отсутствующие в стандарте ANSI C. (Опция `-pedantic` указывает GNU CC печатать предупреждающее сообщение, если какое-нибудь из этих свойств используется.) Чтобы проверить доступность этих свойств в условной компиляции, проверьте предопределенный макрос __GNUC__, который всегда определен под GNU CC.

Эти расширения доступны в C и в Objective C. Большая часть из них также доступна в C++.

4.1 Операторы и Объявления в Выражениях

Составной оператор, заключенный в скобки, может появляться в качестве выражения в GNU C. Это позволяет вам использовать циклы, операторы выбора и локальные переменные внутри выражения.

Напомним, что составной оператор - это последовательность операторов, заключенная в фигурные скобки; в этой конструкции скобки окружают фигурные скобки. Например:

      ({ int y = foo (); int z;
         if (y > 0) z = y;
         else z = - y;
         z; })
является правильным (хотя и несколько более сложным чем необходимо) выражением для абсолютной величины foo().

Последней вещью в составном операторе должно быть выражение, после которого следует точка с запятой; значение этого подвыражения служит значением всей конструкции. (Если вы используете какой-нибудь другой вид оператора последним внутри фигурных скобок, конструкция имеет тип void, и таким образом не имеет значения.)

Это свойство особенно полезно, чтобы делать макроопределения "надежными" (такими, что они вычисляют каждый операнд ровно один раз.) Например, функция "максимум" обычно определяется как макро в стандартном C так:

      #define max(a,b) ((a) > (b) ? (a) : (b))
Но это определение вычисляет либо a, либо b дважды, с неправильными результатами, если операнд имеет побочные эффекты. В GNU C, если вы знаете тип операндов (здесь положим его int), вы можете безопасно определить макро таким образом:

      #define maxint(a,b) \
        ({int _a = (a), _b = (b); _a > _b ? _a : _b; })
Встроенные операторы недопустимы в константых выражениях, таких как значения перечислимых констант, ширина битового поля или начальное значение статической переменной.

Если вы не знаете тип операнда, вы все-таки можете сделать это, но вы должны использовать typeof (см. Раздел [Typeof]) или именование типов (см. Раздел [Именование Типов]).

4.2 Локально Объявляемые Метки

Каждое выражение-оператор является областью, в которой могут быть объявлены локальные метки. Локальная метка - это просто идентификатор; вы можете делать переход на нее с помощью обычного оператора goto, но только изнутри выражения-оператора, к которому она принадлежит.

Объявление локальной метки выглядит так:

           __label__ метка;
или

      __label__ метка1, метка2, ...;
Объвления локальных меток должны идти в начале выражения-оператора сразу после `({` до любого обычного объявления.

Объвление метки определяет имя метки, но не определяет саму метку. Вы должны сделать это обычным способом с помощью `метка:`, внутри выражения-оператора.

Локальные метки полезны, так как выражения-операторы часто используются в макросах. Если макрос содержит вложенные циклы, goto может быть полезен для выхода из них. Однако обычная метка, чьей областью действия является вся функция, не может быть использована: если макрос может быть использован несколько раз в одной функции, метка будет определена в этой функции многократно. Локальная метка избегает этой проблемы. Например:

      #define SEARCH(array, target)                     \
      ({                                                \
        __label__ found;                                \
        typeof (target) _SEARCH_target = (target);      \
        typeof (*(array)) *_SEARCH_array = (array);     \
        int i, j;                                       \
        int value;                                      \
        for (i = 0; i < max; i++)                       \
          for (j = 0; j < max; j++)                     \
            if (_SEARCH_array[i][j] == _SEARCH_target)  \
              { value = i; goto found; }                \
        value = -1;                                     \
       found:                                           \
        value;                                          \
      })

4.3 Метки как Значения

Вы можете взять адрес метки, определенный в текущей функции (или объемлющей функции) с помощью унарной операции `&&`. Значение имеет тип void *. Это значение является константой и может быть использовано везде, где допускается константа этого типа. Например:

      void *ptr;
      ...
      ptr = &&foo;
Чтобы использовать эти значения, вам нужно делать на них переход. Это делается с помощью вычисляемого оператора goto, `goto *выражение; `. Например:

      goto *ptr;
Допустимо любое выражение типа void *.

Один из способов использования этих констант заключается в инициализации статического массива, который будет служить таблицей переходов:

      static void *array[] = { &&foo, &&bar, &&hack };
Затем вы можете выбрать метку с помощью индексации таким образом:

      goto *array[i];
Заметим, что здесь не проверяется, находится ли индекс в допустимых границах - индексация массивов в C никогда не делает этого.

Такой массив значений меток служит цели во многом подобной цели оператора switch. Оператор switch является более понятным, так что используйте его вместо массива, если только задача не является неподходящей для оператора switch.

Другим использованием значений меток является интерпретатор шитого кода. Метки внутри функции интерпретатора могут быть записаны в шитый код для супербыстрой обработки.

Вы можете использовать этот механизм для перехода на код в различных функциях. Если вы так делаете, могут произойти совершенно непредсказуемые вещи. Лучший способ избежать этого - сохранять адреса меток только в автоматических переменных и никогда не передавать их в качестве параметров.

4.4 Вложенные Функции

Вложенная функция - это функция, определенная внутри другой функции. Имя вложенной функции является локальным в блоке, где она определена. Например, здесь мы определяем вложенную функцию с именем square и вызываем ее дважды:

      foo (double a, double b)
      {
        double square (double z) { return z * z; }
 
        return square (a) + square (b);
      }
Вложенная функция имеет доступ ко всем переменным объемлющей функции, которые видны в точке ее определения. Это называется "лексическая область действия". Например, ниже мы показываем вложенную функцию, которая использует наследуемую переменную с именем offset:

      bar (int *array, int offset, int size)
      {
 
        int access (int *array, int index)
 
          { return array[index + offset]; }
 
        int i;
 
        ...
 
        for (i = 0; i < size; i++)
 
          ... access (array, i) ...
      }
Определения вложенных функций разрешаются внутри функций, где допустимы определения переменных; то есть в любом блоке перед первым оператором в блоке.

Можно вызвать вложенную функцию из точки вне области действия ее имени, сохранив ее адрес или передав адрес в другую функцию:

      hack (int *array, int size)
      {
 
        void store (int index, int value)
 
          { array[index] = value; }
 
        intermediate (store, size);
 
      }
Здесь функция intermediate получает адрес функции store в качестве параметра. Если intermediate вызывает store, аргумент, передаваемый в store, используется для записи в array. Но эта техника работает только до тех пор, пока объемлющая функция (в этом примере hack) не возвратит управление.

Если вы пытаетесь вызвать вложенную функцию с помощью ее адреса после того, как объемлющая функция возвратила управление, все полетит к чертям. Если вы пытаетесь вызвать ее после того, как объемлющая область действия закончила работу, и если она ссылается на одну из переменных, которые больше не лежат в области действия, может вам и повезет, но неразумно рисковать. Однако, если вложенная функция не ссылается ни на что, вышедшее из области действия, вы в безопасности.

GNU CC выполняет взятие адреса вложенной функции, используя технику, называемую "trampolines". Бумага, описывающая ее, доступна из 'maya.idiap.ch' в директории 'pub/tmb' в файле 'usenix88-lexic.ps.Z'.

Вложенная функция может делать переход на метку, наследуемую от объемлющей функции, если метка явно объявлена в объемлющей функции (см. Раздел [Локальные Метки]). Такой переход немедленно возвращает в объемлющую функцию, покидая вложенную функцию, которая сделала goto, а также любые промежуточные функции. Пример:

      bar (int *array, int offset, int size)
      {
        __label__ failure;
        int access (int *array, int index)
          {
            if (index > size)
              goto failure;
            return array[index + offset];
          }
        int i;
        ...
        for (i = 0; i < size; i++)
          ... access (array, i) ...
        ...
        return 0;
 
       /* Управление попадает сюда из access,
         если обнаруживается ошибка.  */
       failure:
        return -1;
      }
Вложенная функция всегда имеет внутреннее связывание. Объявление ее с extern является ошибочным. Если вам нужно объявить вложенную функцию до ее определения, используйте auto (который в противном случае бесполезен для объявлений функций).

      bar (int *array, int offset, int size)
      {
 
        __label__ failure;
 
        auto int access (int *, int);
 
        ...
 
        int access (int *array, int index)
          {
 
            if (index > size)
 
              goto failure;
 
            return array[index + offset];
 
          }
 
        ...
      }

4.5 Конструирование Вызовов Функций

Используя встроенные функции, описанные ниже, вы можете записать полученные аргументы функции и вызвать другую функцию с теми же аргументами, не зная количество и типы аргументов.

Вы можете также записать возвращаемое значение этого вызова функции и позже вернуть это значение, не зная, какой тип данных функция пыталась вернуть (если вызывавшая функция ожидает этот тип данных).

__builtin_apply_args ()

Эта встроенная функция возвращает указатель типа void * на данные, описывающие, как выполнять вызов с теми же аргументами, которые были переданы текущей функции.

Функция сохраняет регистр указателя аргументов, адреса структурного значения и все регистры, которые могут быть использованы для передачи аргументов в функцию в блок памяти, выделяемый на стеке. Затем она возвращает адрес этого блока.

__builtin_apply (функция, аргументы, размер)

Эта встроенная функция вызывает 'функцию' (типа void (*)()) с копированием параметров, описываемых 'аргументами' (типа void *) и 'размером' (типа int).

Значение 'аргументы' должно быть значением, которое возвращено __builtin_apply_args (). Аргумент 'размер' указывает размер стековых данных параметров в байтах.

Эта функция возвращает указатель типа void * на данные, описывающие, как возвращать какое-либо значение, которое вернула 'функция'. Данные сохраняются в блоке памяти, выделенном на стеке.

Не всегда просто вычислить подходящее значение для 'размера'. Это значение используется __builtin_apply () для вычисления количества данных, которые должны быть положены на стек и скопированы из области входных аргументов.

__builtin_return (результат)

Эта встроенная функция возвращает значение, описанное 'результатом' из объемлющей функции. Вы должны указать в качестве 'результата' значение, возвращенное __builtin_apply ().

4.6 Именование Типа Выражения

Вы можете дать имя типу выражения, используя объявление typedef с инициализатором. Ниже показано, как определить имя как имя типа выражения:

      typedef имя = выражение;
Это полезно в соединении с возможностью выражений-операторов. Ниже показано, как эти две возможности могут бвть использованы, чтобы определить безопасный макрос "максимум", который оперирует с любым арифметическим типом:

      #define max(a,b) \
        ({typedef _ta = (a), _tb = (b);  \
          _ta _a = (a); _tb _b = (b);     \
          _a > _b ? _a : _b; })
Смысл использования имен, которые начинаются с подчеркиваний для локальных переменных в том, чтобы избегать конфликтов с именами переменных, которые встречаются в выражениях, которые подставляются вместо a и b. В конечном итоге, мы надеемся разработать новую форму синтаксиса объявлений, которая позволит объявлять переменные, чьи области действия начинаются только после их инициализаторов; это будет более надежным способом предотвращения подобных конфликтов.

4.7 Ссылки на Тип с Помощью typeof

Другой способ сослаться на тип выражения - с помощью typeof. Синтаксис использования этого ключевого слова - такой же как и у sizeof, но семантически конструкция действует подобно имени типа, определенного с помощью typedef.

Есть два способа записи аргумента typeof: с выражением и с типом. Ниже показан пример с выражением:

      typeof (x[0](1))
Здесь предполагается, что x является массивом функций; описанный тип является типом значений этих функций.

Ниже показан пример с именем типа в качестве аргумента:

      typeof (int *)
Здесь описанный тип является типом указателей на int.

Если вы пишете заголовочный файл, который должен работать при включении в ANSI C программы, пишите __typeof__ вместо typeof. См. Раздел [Альтернативные Ключевые Слова].

Конструкция typeof может использоваться везде, где допустимо typedef-имя. Например, вы можете использовать ее в объявлении, в приведении или внутри sizeof или typeof.

4.8 Обобщенные L-значения

Составные выражения, условные выражения и приведения позволяются в качестве L-значений, при условии, что их операнды являются L-значениями. Это означает, что вы можете брать их адреса или сохранять в них значения.

Например, составному выражению может быть присвоено что-либо, при условии, что последнее выражение в последовательности является L-значением. Эти два выражения являются эквивалентными:

      (a, b) += 5
      a, (b += 5)
Таким же образом, может быть взят адрес составного выражения. Эти два выражения являются эквивалентными:

      &(a, b)
      a, &b
Условное выражение является допустимым L-значением, если его типом не является void, и при этом обе его ветви являются допустимыми L-значениями. К примеру, эти два выражения являются эквивалентными:

      (a ? b : c) = 5
      (a ? b = 5 : (c = 5))
Приведение является допустимым L-значением, если его операнд является L-значением. Простое присваивание, чьей левой частью является приведение, работает, сначала преобразуя правую часть в указанный тип, а затем к типу внутренней части выражения левой части. После того, как это значение записывается, значение преобразуется обратно к указанному типу, чтобы получить значение присваивания. Таким образом, если a имеет тип char *, следующие два выражения являются эквивалентными:

      (int)a = 5
      (int)(a = (char *)(int)5)
Присваивание с арифметической операцией, такое как '+=', примененное к приведению, выполняет арифметическую операцию, используя тип, получающийся из приведения, и затем продолжает как и в предыдущем случае. Следовательно, эти два выражения являются эквивалентными:

      (int)a += 5
      (int)(a = (char *)(int) ((int)a + 5))
Вы не можете взять адрес L-значения приведения, потому что использование его адреса не могло бы выполняться согласованно.

4.9 Условные Выражения с Опущенными Операндами

Средний операнд в условном выражении может быть опущен. Тогда, если первый операнд не равен нулю, его значение является значением условного выражения.

Следовательно, выражение

      x ? : y
имеет значение x, если оно не равно нулю, в противном случае - значение y.

Этот пример полностью эквивалентен

      x ? x : y
В этом простом случае, возможность опускать средний операнд не особенно полезна. Она становится полезной, когда первый операнд содержит, или может содержать (если это макроаргумент) побочные эффекты. В этом случае повторение операнда в середине может выполнить побочный эффект дважды. Опускание среднего операнда использует уже вычисленное значение без нежелательных эффектов его перевычисления.

4.10 Двухсловные Целые

GNU C поддерживает типы данных для целых, которые вдвое длиннее long int. Просто пишите long long int для знакового целого, или unsigned long long int для беззнакового целого. Чтобы сделать целую константу типа long long int, добавьте суффикс LL к целому. Чтобы сделать целую константу типа unsigned long long int, добавьте суффикс ULL к целому.

4.11 Комплексные Числа

GNU C поддерживает комплексные типы данных. Вы можете объявить как комплексные целые типы, так и комплексные плавающие типы, используя ключевое слово __complex__ .

Например, '__complex__ double x;' объявляет x как переменную, чьи вещественная и мнимая части имеют тип double; '__complex__ short int y;' объявляет y, имеющей вещественную и мнимую части типа short int.

Чтобы записать константу комплексного типа данных, используйте суффикс i или j (любой из них - они эквивалентны). Например, 2.5fi имеет тип __complex__ float, а 3i имеет тип __complex__ int. Такие константы всегда имеют чисто мнимое значение, но вы можете сформировать любое комплексное значение с помощью добавления вещественной константы.

Чтобы извлечь вещественную часть комплеснозначного выражения, пишите '__real__ выражение'. Аналогично, используйте __imag__ для извлечения мнимой части.

Операция '~' выполняет комплексное сопряжение, когда используется над значением комплексного типа.

4.12 Массивы Нулевой Длины

Массивы нулевой длины разрешаются в GNU C. Они являются очень полезными в качестве последнего элемента структуры, который в действительности является заголовком объекта переменной длины:

      struct line {
        int length;
        char contents[0];
      };
 
      {
        struct line *thisline = (struct line *)
          malloc (sizeof (struct line) + this_length);
        thisline->length = this_length;
      }
В стандартном C вы должны бы были дать contents длину 1, который означает, что вы либо должны терять память, либо усложнять аргумент malloc.

4.13 Массивы Переменной Длины

Автоматические массивы переменной длины допустимы в GNU C. Эти массивы объявляются подобно любым другим автоматическим массивам, но с длиной, которая не является константным выражением. Память выделяется в точке объявления и освобождается при выходе из блока. Например:

      FILE *
      concat_fopen (char *s1, char *s2, char *mode)
      {
        char str[strlen (s1) + strlen (s2) + 1];
        strcpy (str, s1);
        strcat (str, s2);
        return fopen (str, mode);
      }
Переход вне области действия массива освобождает память. Переход в область действия недопустим.

Вы можете использовать функцию alloca, чтобы получить эффект во многом подобный массивам переменной длины. Функция alloca допустима во многих других реализациях C (но не во всех). С другой стороны, массивы переменной длины являются более элегантными.

Есть другие отличия между этими двумя методами. Место, выделяемое с помощью alloca, существует пока объемлющая функция не сделает возврат. Место для массива переменной длины освобождается, как только заканчивается область действия имени массива. (Если вы используете как массивы переменной длины, так и alloca в одной и той же функции, освобождение массива переменной длины так же освободит все выделенное после с помощью alloca.)

Вы можете также использовать массивы переменной длины в качестве аргумента функции:

      struct entry
      tester (int len, char data[len][len])
      {
        ...
      }
Длина массива вычисляется один раз при выделении памяти и вспоминается в области действия массива, если вы берете ее с помощью sizeof.

Если вы хотите передать массив первым, а длину после, вы можете использовать предварительное объявление в списке параметров - другое расширение GNU.

      struct entry
      tester (int len; char data[len][len], int len)
      {
        ...
      }
'int len' перед точкой с запятой является предварительным объявлением параметра и служит тому, чтобы сделать имя len известным при разборе объявления data.

Вы можете писать любое число таких предварительных объявлений параметров в списке параметров. Они могут разделяться запятыми или точками с запятыми, но последнее из них должно кончаться точкой с запятой, за которой следуют "реальные" объявления параметров. Каждое предварительное объявление должно соответствовать "реальному" объявлению в имени параметра и типе данных.

4.14 Макросы с Переменным Числом Аргументов

В GNU C макрос может получать переменное число аргументов, во многом подобно тому, как и функция. Синтаксис определения макроса выглядит очень похожим на используемый для функций. Пример:

      #define eprintf(format, args...)  \
       fprintf (stderr, format , ## args)
Здесь args - это остаточный аргумент: он принимает ноль или больше аргументов - столько, сколько содержит вызов. Все они вместе с запятыми между ними образуют значение args, которое подставляется в тело макроса там, где используется args. Таким образом, мы имеем следующее расширение:

      eprintf ("%s:%d: ", input_file_name, line_number)
      ==>
      fprintf (stderr, "%s:%d: " , input_file_name, line_number)
Заметим, что запятая после строковой константы идет из определения eprintf, в то время как последняя запятая идет из значения args.

Смысл использования '##' в обработке случая, когда args не соответствует ни одного аргумента. В этом случае args имеет пустое значение. Тогда вторая запятая в определении становится помехой: если она прошла бы через расширение макроса, мы бы получили что-нибудь подобное:

      fprintf (stderr, "success!\n" , )
что является неправильным синтаксисом C. '##' освобождает от запятой, так что мы получаем следующее:

      fprintf (stderr, "success!\n")
Это специальное свойство препроцессора GNU C: '##' перед остаточным аргументом, который пуст, отбрасывает предшествующую последовательность непробельных символов из макроопределения.

4.15 Массивы Не L-значения Могут Иметь Индексы

Позволяется индексация массивов, которые не являются L-значениями, хотя даже унарная операция '&' не позволяется. Например, это является допустимым в GNU C, хотя и неверным в других диалектах C:

      struct foo {int a[4];};
 
      struct foo f();
 
      bar (int index)
      {
        return f().a[index];
      }

4.16 Арифметика над Указателями на void и на Функции

В GNU C поддерживаются операции сложения и вычитания с указателями на void и на функции. Это делается, принимая размер void или функции равным 1.

Следствием этого является то, что операция sizeof также позволяется над void и над типами функций и возвращает 1.

Опция '-Wpointer-arith' требует предупреждения, если это расширение используется.

4.17 Неконстантные Инициализаторы

Как в стандартном C++ элементы агрегатного инициализатора автоматической переменной не обязаны быть константными выражениями в GNU C. Ниже показан пример инициализатора с элементами, меняющимися во время выполнения:

      foo (float f, float g)
      {
        float beat_freqs[2] = { f-g, f+g };
        ...
      }

4.18 Выражения Конструкторов

GNU C поддерживает выражения конструкторов. Конструктор выглядит как приведение, содержащее инициализатор. Его значение является объектом типа, указанного в приведении, содержащее элементы, указанные в инициализаторе.

Обычно указанный тип является структурой. Предположим, что struct foo и structure объявлены, как показано:

      struct foo {int a; char b[2];} structure;
Ниже показан пример конструирования struct foo с помощью конструктора:

      structure = ((struct foo) {x + y, 'a', 0});
Это эквивалентно написанному ниже:

      {
        struct foo temp = {x + y, 'a', 0};
        structure = temp;
      }
Вы можете также сконструировать массив. Если все элементы конструктора являются (или получаются из) простыми константными выражениями, подходящими для использования в инициализаторах, тогда конструктор является L-значением и может быть приведен к указателю на свой первый элемент, как показано ниже:

      char **foo = (char *[]) { "x", "y", "z" };
Конструкторы массива, чьи элементы не являются простыми константами, не очень полезны, потому что они не являются L-значениями.

4.19 Помеченные Элементы в Инициализаторах

Стандартный C требует, чтобы элементы инициализатора появлялись в фиксированном порядке, в том же самом, в котором элементы массива или структуры инициализируются.

В GNU C вы можете дать элементы в любом порядке, указывая индексы массива или имена полей структуры, к которым они применяются.

Чтобы указать индекс массива, напишите '[индекс]' или '[индекс] =' перед значением элемента. Например,

      int a[6] = { [4] 29, [2] = 15 };
эквивалентно

      int a[6] = { 0, 0, 15, 0, 29, 0 };
Значение индекса должно быть константным выражением, даже если инициализируемый массив является автоматическим.

Чтобы инициализировать диапазон элементов одним и тем же значением, напишите '[первый ... последний] = значение'. Например:

      int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
Заметим, что длина массива равна максимальному указанному значению плюс 1.

В инициализаторе структуры укажите имя инициализируемого поля с помощью 'имяструктуры:' перед значением элемента. Пусть, например, дана следующая структура:

      struct point { int x, y; };
следующая инициализация

      struct point p = { y: yvalue, x: xvalue };
эквивалентна

      struct point p = { xvalue, yvalue };
Другой синтаксис, который имеет то же значение: '.имяструктуры =', как показано ниже:

      struct point p = { .y = yvalue, .x = xvalue };
Вы также можете использовать метку элемента при инициализации объединения, чтобы указать, какой элемент объединения должен использоваться. Например,

      union foo { int i; double d; };
 
      union foo f = { d: 4 };
преобразует 4 в double, чтобы записать его в объединение, использую второй элемент. Напротив, приведение 4 к типу union foo сохранит его в объединении как целое i, поскольку оно целое. (См. Раздел [Приведение к Объединению].)

Вы можете скомбинировать эту технику именования элементов с обычной C инициализацией последовательных элементов. Каждый элемент инициализатора, который не имеет метки, приеняется к следующему элементу массива или структуры. Например,

      int a[6] = { [1] = v1, v2, [4] = v4 };
эквивалентно

      int a[6] = { 0, v1, v2, 0, v4, 0 };

4.20 Диапазоны Case

Вы можете указать диапазон последовательных значений в одной метке case так:

      case LOW ... HIGH:
Будьте внимательны: Пишите пробелы вокруг '...', в противном случае оно может быть разобрано неправильно.

4.21 Приведение к Типу Объединения

Приведение к типу объединения подобно другим приведениям, за тем исключением, что указываемый тип является типом объединения. Вы можете указать тип либо с помощью union тег, либо с помощью typedef имени. Приведение к объединению является в действительности конструктором, а не приведением, и, следовательно, не дает L-значения, как нормальное приведение. (См. Раздел [Конструкторы].)

Типы, которые могут быть приведены к типу объединения, являются типами членов объединения. Таким образом, если даны следующие объединение и переменные:

      union foo { int i; double d; };
      int x;
      double y;
тогда и x, и y могут быть приведены к union foo.

Использование приведения в правой части присваивания переменной типа объединения эквивалентно записи в член объединения:

      union foo u;
      ...
      u = (union foo) x  ==  u.i = x
      u = (union foo) y  ==  u.d = y
Вы можете также использовать приведение к объединению в качестве аргумента функции:

      void hack (union foo);
      ...
      hack ((union foo) x);

4.22 Объявления Атрибутов Функций

В GNU C вы можете объявить определенные вещи о функциях, вызываемых в вашей программе, которые помогают компилятору оптимизировать вызовы функций и более внимательно проверять ваш код.

Ключевое слово __attribute__ позволяет вам указывать специальные атрибуты при создании объявлений. За этим ключеным словом следует описание атрибута в двойных скобках. В данный момент для функций определены восемь атрибутов: noreturn, const, format, section, constructor, destructor, unused и weak. Другие атрибуты, включая section, поддерживаются для объявлений переменных (см. Раздел [Атрибуты Переменных]) и для типов (см. Раздел [Атрибуты Типов]).

Вы можете указывать атрибуты с '__', окружающими каждое ключевое слово. Это позволяет вам использовать их в заголовочных файлах, не заботясь о том, что могут быть макросы с тем же именем. Например, вы можете использовать __noreturn__ вместо noreturn.

noreturn

Несколько стандартных библиотечных функций, таких как abort и exit не могут вернуть управление. GNU CC знает это автоматически. Некоторые программы определяют свои собственные функции, которые никогда не возвращают управление. Вы можете объявить их noreturn, чтобы сообщить компилятору этот факт. Например:

           void fatal () __attribute__ ((noreturn));
 
           void
           fatal (...)
           {
             ... /* Печатает сообщение об ошибке. */ ...
             exit (1);
           }
Ключевое слово noreturn указывает компилятору принять, что функция fatal не может возвратить управление. Тогда он может делать оптимизацию, несмотря на то, что бы случилось, если бы fatal вернула управление. Это делает код немного лучше. Более важно, что это помогает избегать ненужных предупреждений об инициализированных переменных.

Атрибут noreturn не реализован в GNU C версии ранее чем 2.5.

const

Многие функции не используют никаких значений, кроме своих аргументов, и не имеют эффекта, кроме возвращаемого значения. Такая функция может быть объектом исключения общих подвыражений и оптимизации циклов аналогично арифметической операции. Такую функцию следует объявить с атрибутом const. Например,

           int square (int) __attribute__ ((const));
говорит, что гипотетическую функцию square безопасно вызывать меньшее количество раз, чем сказано в программе.

Атрибут const не реализован в GNU C версии ранее 2.5.

Заметим, что функция, которая имеет параметром указатель и использует данные, на которые он указывает, не должна объявляться const. Аналогично, функция, которая вызывает не-const функцию, обычно не должна быть const.

format (тип, строка-индекс, первый-проверяемый)

Атрибут format указывает, что функция принимает аргументы в стиле printf или scanf, которые должны быть проверены на соответствие со строкой формата. Например, объявление

           extern int
           my_printf (void *my_object, const char *my_format, ...)
                 __attribute__ ((format (printf, 2, 3)));
заставляет компилятор проверять параметры в вызове my_printf на соответствие printf-стилю строки формата my_format.

Параметр 'тип' определяет, как строка формата интерпретируется, и должен быть либо printf, либо scanf. Параметр 'строка-индекс' указывает, какой параметр является строкой формата (начиная с 1), а 'первый-проверяемый' является номером первого проверяемого аргумента. Для функций, у которых аргументы не могут быть проверены (таких как vprintf), укажите в качестве третьего параметра ноль. В этом случае компилятор только проверяет строку формата на корректность.

Компилятор всегда проверяет формат для функций ANSI библиотеки printf, fprintf, sprintf, scanf, vprintf, vfprintf, vsprintf, когда такие предупреждения запрашиваются (используя '-Wformat'), так что нет нужды модифицировать заголовочный файл 'stdio.h'.

section ("имя-секции")

Обычно, компилятор помещает генерируемый код в секцию text. Однако, иногда, вам нужны дополнительные секции или же вам нужно, чтобы определенные функции оказались в специальных секциях. Атрибут section указывает, что функция живет в определенной секции. Например, объявление

           extern void foobar (void) __attribute__ ((section ("bar")));
помещает функцию foobar в секцию bar.

constructor

destructor

Атрибут constructor заставляет функцию вызываться автоматически перед выполнением main (). Аналогично, атрибут destructor заставляет функцию вызываться автоматически после того, как main () завершилась или вызвана exit (). Функции с этими атрибутами полезны для инициализации данных.

unused

Этот атрибут, примененный к функции, означает, что функция, возможно, может быть неиспользуемой. GNU CC не будет порождать предупреждение для этой функции.

weak

Атрибут weak приводит к тому, что объявление будет порождаться как слабый символ, а не глобальный. Это прежде всего полезно для определения бибилиотечных функций, которые могут быть переопределены пользовательским кодом, хотя это может быть использовано и с объявлениями не-функций.

alias ("назначение")

Атрибут alias заставляет породить объявление как синоним другого символа, который должен быть указан. Например,

           void __f () { /* делает что-либо */; }
           void f () __attribute__ ((weak, alias ("__f")));
объявляет 'f' слабым синонимом для '__f'.

4.23 Прототипы и Определения Функций в Старом Стиле

GNU C расширяет ANSI C, чтобы позволять прототипам функций перекрывать последующие определения старого стиля. Рассмотрим следующий пример:

      /* Использует прототипы, если компилятор не является старым. */
      #if __STDC__
      #define P(x) x
      #else
      #define P(x) ()
      #endif
 
      /* Прототип объявления функции. */
      int isroot P((uid_t));
 
      /* Определение функции в старом стиле. */
      int
      isroot (x)   /* ??? потеря здесь ??? */
           uid_t x;
      {
        return x == 0;
      }
Предположим тип uid_t оказался short. ANSI C не допускает этот пример, потому что короткие аргументы в старом стиле определений расширяются. Следовательно, в этом примере аргумент определения функции в действительности int, который не соответствует типу аргумента прототипа short.

4.24 Комментарии в C++ Стиле

В GNU C вы можете использовать комментарии C++ стиля, которые начинаются с '//' и продолжаются до конца строки. Многие другие реализации C позволяют такие комментарии, и они, вероятно, будут в будущем стандарте C. Однако, комментарии в C++ стиле не распознаются, если вы указываете '-ansi' или '-traditional', покольку они не совместимы с традиционными конструкциями типа ???.

4.25 Знак Доллара в Идентификаторах

В GNU C вы можете использовать знак доллара в идентификаторах. Это потому что многие традиционные реализации C позволяют такие идентификаторы.

На некоторых машинах, знак доллара разрешается в идентификаторах, если вы указываете '-traditional'. В некоторых системах они разрешаются по умолчанию, даже если вы не используете '-traditional'. Но он никогда не позволяется, если вы указываете '-ansi'.

4.26 Символ ESC в Константах

Вы можете использовать последовательность '\e' в строковой или символьной константе в качестве ASCII символа ESC.

4.27 Выравнивание Типов и Переменных

Ключевое слово __alignof__ позволяет вам узнавать, как выравниваются объекты, или минимальным выравниванием, требуемым для типа. Его синтаксис - такой же как у sizeof.

Например, если целевая машина требует, чтобы значение типа double выравнивалось на 8-байтную границу, тогда __alignof__ (double) равен 8. Это верно для большинства RISC машин. На более традиционных архитектурах __alignof__ (double) равен 4 или даже 2.

Некоторые машины в действительности никогда не требуют выравнивания, они позволяют ссылки на любой тип данных, даже по нечетному адресу. Для этих машин __alignof__ выдает рекомендуемое выравнивание типа.

Когда операндом __alignof__ является L-значение, а не тип, результатом является максимальное выравнивание, которое имеет L-значение. Оно может иметь это выравнивание из-за его типа данных, или потому что оно является частью структуры и наследует выравнивание от этой структуры. Например, после этого объявления

      struct foo { int x; char y; } foo1;
значение __alignof__ (foo1.y) равно, вероятно, 2 или 4 - такое же как __alignof__ (int) хотя тип данных foo1.y сам не требует выравнивания.

Связанное с этим свойство, которое позволяет вам указывать выравнивание объекта - это __attribute__ ((aligned (выравнивание))), см. следующий раздел.

4.28 Указание Атрибутов Переменных

Ключевое слово __attribute__ позволяет вам указывать специальные атрибуты переменных или полей структуры. За этим ключевым словом следует спецификация атрибута в двойных скобках. Восемь атрибутов поддерживаются в данный момент для переменных: aligned, mode, nocommon, packed, section, transparent_union, unused, weak. Другие атрибуты допустимы для функций (см. Раздел [Атрибуты Функций]) и для типов (см. Раздел [Атрибуты Типов]).

Вы можете указывать атрибуты с '__', окружающими каждое ключевое слово. Это позволяет вам использовать их в заголовочных файлах, не заботясь о том, что могут быть макросы с тем же именем. Например, вы можете использовать __aligned__ вместо aligned.

aligned (выравнивание)

Этот атрибут определяет минимальное выравнивание для переменной или поля структуры, измеряемое в байтах. Например, объявление

           int x __attribute__ ((aligned (16))) = 0;
заставляет компилятор размещать глобальную переменную x по 16-байтной границе. На 68040 это может быть использовано вместе с asm выражением, чтобы использовать инструкцию move16, которой требуются операнды, выравненные на 16 байт.

Вы можете также указать выравнивание полей структуры. Например, для создания пары int, выравненной на границу двойного слова, вы могли бы написать:

           struct foo { int x[2] __attribute__ ((aligned (8))); };
Это является альтернативой созданию объединения с double членом, который заставляет выравнивать объединение на границу двойного слова.

Невозможно определять выравнивание функций, выравнивание функций определяется требованиями машины и не может быть изменено. Вы не можете указать выравнивание для typedef имени, потому что такое имя является только синонимом, а не отдельным типом.

Как в предыдущих примерах, вы можете явно указать выравнивание (в байтах), которое вы хотели бы, чтобы использовал компилятор для данной переменной или поля структуры. В качестве альтернативы, вы можете оставить размер выравнивания и только попросить компилятор выравнивать переменную или поле по максимальному полезному выравниванию для целевой машины, для которой вы компилируете. Например, вы могли бы написать:

           short array[3] __attribute__ ((aligned));
Атрибут aligned может только увеличить выравнивание, но вы можете уменьшить его с помощью указания packed. См. ниже.

Заметим, что эффективность атрибутов aligned может быть ограничена ограничениями вашего линкера. Во многих системах, линкер может только обрабатывать выравнивание переменных, не превышающее определенного предела. (Для некоторых линкеров максимальное поддерживаемое выравнивание может быть очень и очень малым.) См. документацию по вашему линкеру для дальнейшей информации.

mode (вид)

Этот атрибут указывает тип данных для объявления - тип, который соответствует виду 'вид'. Это в действительности позволяет вам требовать целый или плавающий тип в соответствии с его размером. Вы можете также указать вид 'byte', чтобы указать вид, соответствующий однобайтовому целому, 'word' для вида однословного целого и 'pointer' для вида, используемого для представления указателей.

nocommon

Этот атрибут указывает GNU CC помещать переменную "общей", а выделять место для нее прямо.

packed

Атрибут packed указывает, что переменная или поле структуры должно иметь минимальное возможное выравнивание - один байт для переменной и один бит для поля, если вы не указали большее значение с помощью атрибута aligned.

Ниже показана структура, в которой поле x запаковано так, что оно непосредственно следует за a:

           struct foo
           {
             char a;
             int x[2] __attribute__ ((packed));
           };
section ("имя-секции")

Обычно компилятор помещает объекты, которые он генерирует в секции типа data и bss. Однако, иногда вам нужны дополнительные секции, или вам нужно, чтобы определенные переменные оказались в специальных секциях, например, чтобы отобразить специальное оборудование. Атрибут section указывает, что переменная (или функция) живет в определенной секции. Например, эта маленькая программа использует несколько особых имен секций:

           struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
           struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
           char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
           int init_data_copy __attribute__ ((section ("INITDATACOPY"))) = 0;
 
           main()
           {
             /* Инициализируем указатель стека */
             init_sp (stack + sizeof (stack));
 
             /* Инициализируем инициализированные данные */
             memcpy (&init_data_copy, &data, &edata - &data);
 
             /* Включаем последовательные порты */
             init_duart (&a);
             init_duart (&b);
           }
Используйте атрибут section с инициализированным определением глобальной переменной, как показано в примере. GNU CC выдает предупреждение и игнорирует атрибут section в неинициализированном объявлении переменной.

transparent_union

Этот атрибут, примененный к переменной-аргументу функции, который является объединением, означает передавать аргумент, таким же образом, каким передавался бы первый член объединения. Вы можете также использовать этот атрибут с typedef для типа данных объединения, затем он применяется ко всем аргументам функций с этим типом.

unused

Этот атрибут, примененный к переменной, означает, что переменная, возможно, может быть неиспользуемой. GNU CC не будет порождать предупреждение для этой переменной.

weak

Атрибут weak описан в Разделе [Атрибуты Функций].

Для указания многочисленных атрибутов разделяйте их запятыми внутри двойных скобок. Например: '__attribute__ ((aligned (16), packed))'.

4.29 Указание Атрибутов Типов

Ключевое слово __attribute__ позволяет вам указывать специальные атрибуты struct и union типов при их определении. За этим ключевым словом следует спецификация атрибута в двойных скобках. Три атрибута поддерживаются в данный момент для типов: aligned, packed, transparent_union. Другие атрибуты допустимы для функций (см. Раздел [Атрибуты Функций]) и для переменных (см. Раздел [Атрибуты Переменных]).

Вы можете указывать атрибуты с '__', окружающими каждое ключевое слово. Это позволяет вам использовать их в заголовочных файлах, не заботясь о том, что могут быть макросы с тем же именем. Например, вы можете использовать __aligned__ вместо aligned.

Вы можете указывать атрибуты aligned и transparent_union либо в typedef объявлении, либо сразу после закрывающей скобки полного определения enum, struct или union типа, а атрибут packed - только после закрывающей скобки определения.

aligned (выравнивание)

Этот атрибут определяет минимальное выравнивание (в байтах) для переменных указанного типа. Например, объявление

           struct S { short f[3]; } __attribute__ ((aligned (8));
           typedef int more_aligned_int __attribute__ ((aligned (8));
заставляет компилятор гарантировать, что каждая переменная, чей тип - struct S или more_aligned_int будет размещаться и выравниваться на по меньшей мере 8-байтовой границе.

Заметим, что выравнивание любого данного struct или union типа, требуемое стандартом ANSI C будет по меньшей мере максимальным выравниванием из выравниваний всех членов рассматриваемого struct или union типа.

Как в предыдущем примере, вы можете явно указать выравнивание (в байтах), которое вы хотите, чтобы использовал компилятор для данного типа. В качестве альтернативы, вы можете оставить размер выравнивания и только попросить компилятор выравнивать тип по максимальному полезному выравниванию для целевой машины, для которой вы компилируете. Например, вы могли бы написать:

           struct S { short f[3]; } __attribute__ ((aligned));
Атрибут aligned может только увеличить выравнивание, но вы можете уменьшить его с помощью указания packed. См. ниже.

Заметим, что эффективность атрибутов aligned может быть ограничена ограничениями вашего линкера. Во многих системах, линкер может только обрабатывать выравнивание переменных, не превышающее определенного предела. (Для некоторых линкеров максимальное поддерживаемое выравнивание может быть очень и очень малым.) См. документацию по вашему линкеру для дальнейшей информации.

packed

Этот атрибут, примененный к определению enum, struct или union типа, указывает, что для представления этого типа должно быть использовано минимальное количество памяти.

Указание этого атрибута для enum, struct или union типа эквивалентно указанию атрибута packed для каждого члена структуры или объединения. Указание флага '-fshort-enums' в командной строке эквивалентно указанию атрибута packed для всех описаний enum.

Вы можете указывать этот атрибут только после закрывающей скобки описания enum, но не в typedef объявлении.

transparent_union

Этот атрибут, присоединенный к описанию типа union, показывает, что любая переменная этого типа при передаче функции должна передаваться также, как передавался бы первый член объединения. Например:

           union foo
           {
             char a;
             int x[2];
           } __attribute__ ((transparent_union));
Для указания многочисленных атрибутов разделяйте их запятыми внутри двойных скобок. Например: '__attribute__ ((aligned (16), packed))'.


Вперед Назад Содержание


Спонсоры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2022 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру