《C++Primer5th》中文版第124页
C++语言没有明确规定大多数二元运算符的求值顺序,
给编译器优化留下了余地。
这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,这个是否可以接受?
1.首先可以知道优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。
比如:
int i=f1()*f2();
在这里虽然f1和f2在乘法之前被调用,但是f1先调用还是f2先调用却不得而知。
2.再比如结合律
int i=0;
cout<<i<<" "<<++i<<endl;
结果可能是0 1或者是1 1.
因为虽然<<是左结合,但是对于那些没有明确规定运算对象的求值顺序的运算符而言,求值顺序就和优先级,以及结合律无关。
所以上面的式子是未定义的,即如果表达式指向并且修改了同一个对象,这样的行为就是未定义的
- 逻辑与&&
- 逻辑非||
- 条件?:
-逗号,
上面四种运算符明确规定了运算对象的求值顺序。
3.C++手册
几乎所有 C++ 运算符的求值顺序(包括函数调用表达式中的函数参数求值顺序和任何表达式中子表达式的求值顺序)都是未指定的。编译器能以任何顺序求值,并可以在再次求值相同表达式时选择另一顺序。
例子:
表达式 f1() + f2() + f3()
由于 operator+ 的从左到右结合性分析为 (f1() + f2()) + f3() ,但运行时对 f3 的函数调用可能首先、最后,或在 f1() 和 f2() 之间求值。
4.序列点规则(以下内容来自C++手册)
序列点规则 (C++11 前)
定义
求值可能产生副效应:即访问 volatile 左值所指代的对象、修改对象、调用库 I/O 函数或调用做任何这些动作的函数。
序列点( sequence point )是执行序列中的点,在该点所有来自序列中先前求值的副效应均已完成,而后继求值的副效应都未开始。
规则
每个完整表达式结尾(典型地在分号)有一个序列点。
调用函数时(无论该函数是否内联,无论是否使用函数调用语法),所有函数参数的求值(若存在)后有一个序列点,它在函数体内的任何表达式或语句执行前发生。
复制函数返回值后,和函数外任何语句的执行前有一个序列点。
一旦函数执行开始,则在被调用函数完成前,不求值来自调用方函数的表达式(函数不能交错)。
每个使用内建(非重载)运算符的下列四种表达式的求值中,表达式 a 的求值后有一个序列点。
a && b
a || b
a ? b : c
a , b
未定义行为
- 前后序列点间,至多可以修改标量对象的存储值一次,否则行为未定义。
i = ++i + i++; // 未定义行为
i = i++ + 1; // 未定义行为( C++17 前)
i = ++i + 1; // 未定义行为( C++11 前)
++ ++i; // 未定义行为( C++11 前)
f(++i, ++i); // 未定义行为( C++17 前)
f(i = -1, i = -1); // 未定义行为( C++17 前)
- 前后序列点间,访问表达式求值所修改的标量对象的先前值,必须只为确定要存储的值。若以任何其他方式访问,则行为未定义。
cout << i << i++; // 未定义行为( C++17 前)
a[i] = i++; // 未定义行为( C++17 前)