【GNU笔记】【C扩展系列】表达式中的语句和声明 Statements and Declarations in Expressions
表达式中的语句和声明 Statements and Declarations in Expressions
在GNU C中,用括号括起来的复合语句可以作为一个表达式出现。这允许你在表达式中使用循环(loops)、switches和局部变量(local variables)。
回顾一下,复合语句是一个由大括号包围的语句序列;在这个结构(construct)中,小括号括在大括号外面。例如:
({ int y = foo (); int z;
if (y > 0) z = y;
else z = - y;
z; })
是foo ()
绝对值的有效表达式(尽管比必要的表达式稍微复杂一些)。
复合语句中的最后一件事应该是一个表达式,后面跟一个分号;
这个子表达式的值是整个结构的值。(如果你在大括号内最后使用其他类型的语句,则该结构的类型为void
,因此实际上没有任何值)。
这个特性在使宏定义 “安全”(使它们对每个操作数只计算一次)方面特别有用。例如,"maximum"函数在标准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
中所做的那样)可能会导致变量隐藏(Variable Shadowing),所以这个使用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); })
常量表达式中不允许嵌入语句,例如枚举常量(enumeration constant)的值、位域(bit-field)的宽度或静态变量(static variable)的初始值。
如果你不知道操作数的类型,你仍然可以这样做,但你必须使用typeof
or __auto_type
(参见Typeof)。
在G++中,语句表达式的结果值经历数组和函数指针衰减,并通过值返回给封闭表达式(enclosing expression)。例如,如果A
是一个类,那么
A a;
({a;}).Foo ()
构造了一个临时的A
对象来保存语句表达式的结果,并用于调用Foo
。因此,Foo
观察到的这个this
指针不是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的某些版本包含了使用语句表达式的头文件,这恰恰导致了这个bug。)
不允许使用goto
跳转到语句表达式,或在语句表达式外部使用switch
语句,在语句表达式内部使用带有case
或default
标签。使用计算goto
(参见Labels as Values)跳转到语句表达式会产生未定义的行为。允许跳出语句表达式,但如果语句表达式是更大的表达式的一部分,则未指定该表达式的哪些其他子表达式已被计算,除非语言定义要求在语句表达式之前或之后计算某些子表达式。语句表达式中的break
或continue
语句用于while
、do
或for
循环或switch
语句的条件或for
语句的init或increment表达式,如果有的话就跳到外部循环或switch
语句(否则就是错误),而不是跳到它出现的条件或init或increment表达式的循环或switch语句中。在任何情况下,与函数调用一样,语句表达式的评估不会与包含表达式的其他部分的评估交错进行。比如说。在任何情况下,与函数调用一样,语句表达式的计算不会与包含表达式的其他部分的计算交叉。例如,
foo (), (({ bar1 (); goto a; 0; }) + bar2 ()), baz();
调用foo
andbar1
,不调用baz
,但可能会也可能不会调用bar2
。如果bar2
被调用,则在foo
之后和bar1
之前调用它。