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-11.4.0/gcc/Statement-Exprs.html