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 (см. Ссылка на тип с помощью typeof).
В 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.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-13.3.0/gcc/Statement-Exprs.html