6.12.1 Операторы и объявления в выражениях
Составной оператор, заключённый в скобки, может использоваться как выражение в GNU C. Это позволяет использовать циклы, операторы switch и локальные переменные внутри выражения.
Напомним, что составной оператор — это последовательность операторов, заключённая в фигурные скобки; в этой конструкции фигурные скобки обрамляются круглыми. Например:
({ 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 были заголовочные файлы с выражениями-операторами, приводившими именно к этой ошибке.)
Переход внутрь выражения-оператора с помощью 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-15.3.0/gcc/Statement-Exprs.html