88 C++的参数计算顺序
本节讲的是是一些 C++的提示、技巧之类的东西,下一节就是移动语义了!
1. 参数求值顺序
这样有众多参数的函数中,参数求值的顺序是什么?
Cherno 认为如果你不知道这个答案是正常且合理的,因为它涉及到对编译器如何编译代码的深入了解,你并不会在日常编程中考虑到这个“普遍的”问题。
用一个例子来考虑这个问题,请给出下面示例中 PrintSum 函数的运行结果:
void PrintSum(int a, int b)
{
std::cout << a << " + " << b << " = " << (a + b) << std::endl;
}
int main()
{
int value = 0;
PrintSum(value++, value++);
std::cin.get();
}
value++是一个后增的操作符,意思是初始值应该先传入再递增,所以我认为答案是 1,0
但这个问题的答案应该是“未定义行为”(undefined behavior),C++标准并没有真正定义这种情况下应该发生什么。这里的“未定义行为”就是说它会根据便一起的不同而变化,完全依赖于 C++编译器将代码转换成机器码的实际实现。
Debug 模式
Release 模式
改成前增:
所以 Release 模式下的后增运算到底发生了什么呢?
原因是这种情况下,编译器实际上被允许并行地计算出这些是什么,不必先按照指定的顺序对这些参数求值,然后查看它们是什么(有点像constant folding,也就是预先计算常量表达式的结果,如: int a = 1 x 2 ,不会在 runtime 计算,而是compile-time 优化为 int a = 2)
这在 C++17 标准以前是可以的,因为 C++17 标准中加入了一个新规则:后缀表达式必须在别的表达式之前被计算 所以运行结果会发生变化:
PrintSum(value++, value++);
// C++ 14 :
// Release: 0 + 0 = 0
// Debug : 1 + 0 = 1
// C++ 17 :
// Release: 1 + 0 = 1
// Debug : 1 + 0 = 1
不过这里的顺序仍然没有定义
用 gcc 和 C++20 标准运行一下:
截图看出相比于 VS 的 MSVC 编译器,这里 gcc 至少告诉我们这个 value 操作可能是未定义的,也得到了结果
而 clang 的编译结果是相反的:
所以如果你在工作面试或者编程测试中遇到了这个问题,正确答案是“未定义”,因为 C++实际上没有提供一个规范,一个定义,来说明在这种情况下应该发生什么,形参或实参应该按照什么顺序求值。但如果你提到 C++17 后不能同时计算就会加分了。但是这个顺序没有在规范中定义,这意味着你在技术上无法知道计算顺序是什么。