函数调用的顺序
Order of function calling
函数在以下表达式中调用的顺序是什么:
a = f1(23, 14) * f2(12/4) + f3();
它取决于编译器吗?
在C和C++中,每个操作数的求值顺序是未指定,这意味着,在您的情况下,根据标准,函数调用的顺序是未指定的。
请注意,它是未指定,不是实现定义的。
在C和C++中都是未指定的。
参考文献:
C++03标准:第5节:表达式,第4段:
除非注意到[例如,用于&&和||]的特殊规则,否则单个运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序是未指定的。
C99标准:第6.5节:
运算符和操作数的分组由语法指示。72)除了后面指定的(对于函数调用(),&;,||,?:,和逗号运算符),子表达式的求值顺序和副作用发生的顺序都未指定。
C++:标准保证在到达序列点之前计算在序列点之前遇到的所有表达式。在您的情况下,=
和;
之间没有序列点,因此顺序未指定。
在这种情况下无法预测订单。它不依赖于编译器,它是未指定的;即使使用相同的编译器,也可以得到不同的求值顺序。
与其说这是未指定的,故事的结尾,不如让我解释一下如何评估它。最重要的是,不要混淆求值顺序(操作数)的概念和运算符优先级的概念,它们是不同的。
-
在这种情况下,当我们只有基本的数学运算符时,整个表达式本身的求值顺序很容易理解。但是,当涉及到其他C运算符时,它就不那么琐碎了。因此,总是通过找出子表达式的求值顺序来开始求值表达式:
-
运算符优先级规则保证对每个编译器都是相同的。它们指出二进制乘法运算符(*)比二进制加法运算符(+)具有更高的优先级。这两个运算符的优先级都高于赋值运算符(=)。因此,可以保证首先计算子表达式
f1(23, 14) * f2(12/4)
,然后其结果将成为与f3()
相加的操作数,最后将结果分配给a
。 -
为了说明这一点,表达式等于
a = ( (f1(23, 14) * f2(12/4)) + f3() );
。 -
因此我们得到了子表达式
f1(23, 14) * f2(12/4)
。操作数本身求值的求值顺序是未指定的行为,这意味着我们无法知道首先求值的是f1操作数还是f2操作数。编译器可以自由地从左到右或从右到左对它们进行求值,并且不需要记录哪种方式适用。我们所知道的是,编译器将始终从左到右或从右到左进行求值。 -
让我们假设特定的编译器从左到右进行计算。然后将首先评估CCD_ 8。接下来的问题是,函数的哪个参数将首先进行评估。同样的情况也适用于此,函数参数的求值顺序也未指定。在这种情况下,这并不重要,因为这两个参数都是整数常量。
-
按照从左到右的计算顺序,编译器将首先计算(并执行)f1,然后计算f2,然后将结果相乘,并将其存储在一个临时的、不可见的变量中。然后,它将计算f3,然后执行加法,并最终将结果分配给
a
。
这里学到的重要教训是:由于子表达式的求值顺序是未指定的,因此每个子表达式不应包含任何取决于求值顺序的副作用。在本例中,如果f1和f2都将数字1或2分别写入全局变量,则如果编译器从左到右求值,则该全局变量最终的值为2,但如果从右到左求值,则为1。这样的代码在一个编译器上可以完美地工作,但在另一个编译器中却崩溃得很惨。
- 函数调用中参数的顺序重要吗
- x64 函数调用参数推送/移动顺序 (MSVC)
- COUT 内部函数调用的顺序
- 构造函数的初始值设定项列表中的函数调用是否按顺序排序?
- C++:cout 和函数调用之间的计算顺序
- 当指定初始化程序的顺序和字段声明不一致时,clang可以删除函数调用
- 函数调用的顺序
- 构造函数和析构函数调用的顺序
- 析构函数调用的顺序C++
- 在虚拟继承中构造函数调用的顺序是什么
- 可变模板扩展中函数调用的顺序
- 如何将 etrace 与动态库结合使用,以按时间顺序跟踪 C++ 中的函数调用
- STL 中构造函数调用的顺序
- 此代码是否依赖于函数调用顺序未定义的行为
- 强制实施函数调用的顺序
- 函数调用的顺序
- 使用继承时构造函数/析构函数调用的顺序
- 链式静态函数调用之间的参数求值顺序
- 在块程序结束时析构函数调用的顺序
- 无法理解析构函数调用顺序