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