Spec-Zone.ru › GCC 12

6.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; })

Обратите внимание, что введение объявлений переменных (как мы делаем в maxint) может привести к затенению переменных, поэтому, хотя этот пример с использованием макроса max даёт правильные результаты:

int _a = 1, _b = 2, c;
c = max (_a, _b);

в этом примере с использованием maxint так не будет:

int _a = 1, _b = 2, c;
c = maxint (_a, _b);

Эта проблема может, например, возникнуть, когда мы используем этот шаблон рекурсивно, например:

#define maxint3(a, b, c) \
  ({int _a = (a), _b = (b), _c = (c); maxint (maxint (_a, _b), _c); })

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

Если вы не знаете тип операнда, вы всё равно можете это сделать, но должны использовать typeof или __auto_type (см. Тип).

В G++, результат значения оператора выражения претерпевает разложение массива и указателей на функции и возвращается по значению в окружающее выражение. Например, если A — это класс, то

A a;

({a;}).Foo ()

создаёт временный A объект для хранения результата выражения оператора, и этот объект используется для вызова Foo. Следовательно, указатель this наблюдаемый Foo не является адресом a.

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

#define macro(a)  ({__typeof__(a) b = (a); b + 3; })
template<typename T> T function(T a) { T b = a; return b + 3; }

void foo ()
{
  macro (X ());
  function (X ());
}

имеет разные места, где уничтожаются временные объекты. В случае macro, временный X уничтожается сразу после инициализации b. В случае function этот временный объект уничтожается при возвращении из функции.

Эти соображения означают, что использование выражений операторов такого вида в заголовочных файлах, предназначенных для работы с C++, вероятно, не лучшая практика. (Обратите внимание, что некоторые версии GNU C Library содержали заголовочные файлы с использованием выражений операторов, которые приводили к точно такой же ошибке.)

Переход внутрь выражения оператора с помощью goto или использование оператора switch вне выражения оператора с меткой case или default внутри выражения оператора запрещено. Переход в выражение оператора с вычисленной меткой goto (см. Метки как значения) приводит к неопределённому поведению. Переход из выражения оператора разрешён, но если выражение оператора является частью большего выражения, то не определено, какие другие подвыражения этого выражения были вычислены, за исключением случаев, когда определение языка требует вычисления определённых подвыражений до или после выражения оператора. Оператор break или continue внутри выражения оператора, используемого в while, do или цикле for или условии оператора switch или выражениях инициализации или инкремента оператора for переходит к внешнему циклу или оператору switch при его наличии (иначе это ошибка), а не к циклу или оператору switch в условии или выражении инициализации или инкремента которого он появляется. В любом случае, как и при вызове функции, вычисление выражения оператора не переплетается с вычислением других частей содержащего выражения. Например,

foo (), (({ bar1 (); goto a; 0; }) + bar2 ()), baz();

вызывает foo и bar1 и не вызывает baz, но может или не может вызвать bar2. Если bar2 вызывается, то он вызывается после foo и до bar1.

Далее: Локальные метки, Вернуться к: Расширения C [Содержание][Индекс]

© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Statement-Exprs.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API