求值顺序
来自 cppreference.cn
任何 C 运算符的操作数的求值顺序,包括函数调用表达式中函数参数的求值顺序,以及任何表达式中子表达式的求值顺序都是未指定的(除非下面另有说明)。编译器将以任何顺序求值,并且当再次求值相同的表达式时,可能会选择另一种顺序。
在 C 语言中没有从左到右或从右到左的求值概念,这不应与运算符的从左到右和从右到左的结合性混淆:表达式 f1() + f2() + f3()
由于运算符+的从左到右结合性而被解析为 (f1() + f2()) + f3()
,但在运行时,对 f3
的函数调用可能首先、最后,或在 f1()
或 f2()
之间进行求值。
目录 |
[编辑] 定义
[编辑] 求值
编译器为每个表达式或子表达式执行两种求值(两者都是可选的)
- 值计算:计算表达式返回的值。这可能涉及确定对象的身份(左值求值)或读取先前分配给对象的值(右值求值)
- 副作用:访问(读或写)由volatile左值指定的对象,修改(写入)对象、原子同步(C11 起),修改文件,修改浮点环境(如果支持),或调用执行任何这些操作的函数。
如果表达式没有产生副作用,并且编译器可以确定该值未被使用,则该表达式可能不会被求值。
[编辑] 排序
“sequenced-before”(先行)是同一线程中求值之间的一种非对称、传递性的成对关系(如果涉及原子类型和内存屏障,它可能会跨线程扩展)。
- 如果在子表达式 E1 和 E2 之间存在一个序列点,那么 E1 的值计算和副作用都先行于 E2 的每个值计算和副作用
|
(C11 起) |
[编辑] 规则
1) 在所有函数参数和函数指示符的求值之后,以及在实际函数调用之前,存在一个序列点。
2) 在以下二元运算符的第一个(左)操作数求值之后,以及在第二个(右)操作数求值之前,存在一个序列点:
&&
(逻辑与)、||
(逻辑或)和 ,
(逗号)。3) 在条件运算符
?:
的第一个(左)操作数求值之后,以及在第二个或第三个操作数(以实际求值的为准)求值之前,存在一个序列点。
5) 在完整声明符的末尾存在一个序列点。
6) 在库函数返回之前立即存在一个序列点。
|
(C99 起) |
9) 任何运算符的操作数的值计算(但不是副作用)先行于运算符结果的值计算(但不是其副作用)。
10) 直接赋值运算符和所有复合赋值运算符的副作用(修改左参数)先行于左右参数的值计算(但不是副作用)。
11) 后增量和后减量运算符的值计算先行于其副作用。
12) 不先行于或不后行于另一个函数调用的函数调用是不可确定顺序的(构成不同函数调用的 CPU 指令不能交错,即使函数是内联的)。
13) 在初始化列表表达式中,所有求值都是不可确定顺序的。
14) 对于不可确定顺序的函数调用、复合赋值运算符的操作以及增量和减量运算符的前缀和后缀形式,它们都是单个求值。 |
(C11 起) |
[编辑] 未定义行为
1) 如果对标量对象的副作用相对于对同一标量对象的另一个副作用是无序的,则行为是未定义的。
i = ++i + i++; // undefined behavior i = i++ + 1; // undefined behavior f(++i, ++i); // undefined behavior f(i = -1, i = -1); // undefined behavior
2) 如果对标量对象的副作用相对于使用同一标量对象的值进行的值计算是无序的,则行为是未定义的。
f(i, i++); // undefined behavior a[i] = i++; // undefined bevahior
3) 只要至少有一个允许的子表达式排序允许这种无序的副作用,上述规则就适用。
[编辑] 另请参阅
运算符优先级,它定义了如何从源代码表示构建表达式。
C++ 文档,关于求值顺序
|