求值顺序
除下列标出者,任意 C 运算符的运算数求值顺序,包括函数调用表达式的函数参数求值顺序,及任何表达式的子表达式求值顺序都是未指定的。编译器会以任意顺序对其求值,而且在同一表达式被再度求值时可选用另一种顺序。
C 中没有从左到右或从右到左求值的概念,这不会与运算符的从左到右或从右到左结合性混淆:表达式
f1() + f2() + f3()
被分析成 (f1() + f2()) + f3()
,因为 operator+ 的从左到右结合性,但运行时对 f3
的函数调用可以最先、最后,或在
f1()
与 f2()
之间求值。
定义
求值
对于每个表达式或子表达式,有二种求值为编译器进行(两者都是可选的):
- 值计算( value computation ):计算表达式所返回的值。这可以涉及到确定对象身份(左值求值)或读取之前赋给对象的值(右值求值)
- 副效应( side effect ):访问(读或写)以 volatile 左值指代的对象、修改(写)对象、原子同步 (C11 起)、修改文件、修改浮点环境(若支持)或调用进行上述操作的函数。
若表达式不产生副效应,且编译器能确定其值不被使用,则表达式可以不求值。
排序
“先序于( sequenced before )”是一种同一线程内求值的不对称、传递性、成对的关系(若引入原子类型和内存屏障,则它可以扩展到线程间)。
- 若在子表达式 E1 和 E2 间存在序列点( sequence point ),则 E1 的值计算和副效应都先序于 E2 的所有值计算和副效应
|
(C11 起) |
规则
&&
(逻辑与)、
||
(逻辑或),及 ,
(逗号)。?:
的第一(左)运算数求值后,第二或第三运算数(无论何者被求值)前,有一个序列点。
5)
在完整声明器的结尾,有一个序列点。
6)
在紧接库函数返回前,有一个序列点。
|
(C99 起) |
9)
属于运算符的运算数的值计算(但非副效应)先序于运算符的值计算(但非其副效应)。
10)
直接赋值运算符与所有复合赋值运算符的副效应(修改左参数)后序于左右参数的值计算(但非其副效应)。
11)
后自增和后自减运算符的值计算先序于其副效应。
12)
既非先序于亦非后序于另一函数调用的函数调用是非确定顺序的(构成不同函数调用的 CPU
指令不可能交错,即使函数被内联)。
13) 在初始化器列表表达式中,所有求值都是非确定顺序的
14)
考虑到非确定顺序的函数调用,复合赋值运算符,及自增减运算符的前后缀形式都是单独求值。
|
(C11 起) |
未定义行为
i = ++i + i++; // 未定义行为 i = i++ + 1; // 未定义行为 f(++i, ++i); // 未定义行为 f(i = -1, i = -1); // 未定义行为
f(i, i++); // 未定义行为 a[i] = i++; // 未定义行为
参阅
运算符优先级,定义自源代码表示构建表达式的方式。