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.
Далее: Локальные метки, Вернуться к: Расширения C [Содержание][Индекс]
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-9.5.0/gcc/Statement-Exprs.html