对于以下使用gcc和clang的代码,我得到了不同的结果,我相信这不是一个严重的错误,但是我想知道哪个结果与标准更一致?非常感谢您的回复。
我使用gcc(Ubuntu 7.3.0-27ubuntu1〜18.04)7.3.0和clang版本6.0.0-1ubuntu2(tags / RELEASE_600 / final)
#include <stdio.h>
int get_1(){
printf("get_1\n");
return 1;
}
int get_2(){
printf("get_2\n");
return 2;
}
int get_3(){
printf("get_3\n");
return 3;
}
int get_4(){
printf("get_4\n");
return 4;
}
int main(int argc, char *argv[])
{
printf("%d\n",get_1() + get_2() - (get_3(), get_4()));
return 0;
}
gcc的结果是
get_3
get_1
get_2
get_4
-1
并且clang的结果是
get_1
get_2
get_3
get_4
-1
答案 0 :(得分:8)
C在评估某些运算符的操作数时不施加顺序。在C标准中,按顺序点施加评估顺序。当存在序列点时,该语言的良好实现必须先评估序列点左侧的所有内容,然后才能开始评估右侧的内容。 +
和-
运算符不包含任何序列点。这是5.1.2.3 p2
在执行序列中某些指定的点(称为顺序点)上,以前评估的所有副作用都应完整,并且以后评估的副作用都不应发生。
在您的表情中
get_1() + get_2() - (get_3(), get_4())
您有+
,-
和逗号,
运算符。只有逗号强制使用求值顺序,+
和-
则没有。
答案 1 :(得分:5)
,
和get_3()
之间的get_4()
是printf("%d\n",get_1() + get_2() - (get_3(), get_4()));
中的唯一序列点,get_x
调用可以按编译器定义的任何顺序进行因为get_3()
发生在get_4()
之前。
您正在看到未指定行为的结果。
答案 2 :(得分:2)
正在使用两个不同但相关的术语:运算符优先级和评估顺序。
运算符优先级决定解析顺序:
()
。没什么奇怪的,它们是后缀,属于它们的运算符,即函数名。接下来,我们有二进制+
和-
运算符。它们属于同一运算符组“加法运算符”,并且具有相同的优先级。发生这种情况时,该组操作员的 operator关联性决定应按哪个顺序对其进行解析。
对于加法运算符,运算符的关联性是从左到右。这意味着该表达式可以保证被解析为(get_1() + get_2()) - ...
。
一旦按照上面的顺序对运算符的优先级进行了排序,我们就会知道哪些操作数属于哪些运算符。但这并没有说明表达式将按什么顺序执行。这就是评估顺序的来源。
通常C用干燥的标准术语表示:
除非后面有特别说明,否则子表达式的副作用和值计算都是无序列的。
用通俗易懂的英语,这意味着在大多数情况下,操作数的求值顺序是不确定的。
对于加法运算符+
和-
,这是正确的。给定a + b
,我们不知道a
或b
将首先执行。评估的顺序是不确定的-编译器可以按照自己喜欢的任何顺序执行它,不需要记录如何做,甚至不必因情况而异。
C标准有意将此保留为未指定,以允许不同的编译器以不同的方式解析表达式。从本质上讲,它们使他们的表达式树算法成为编译器的商业秘密,以使某些编译器在自由市场上能够比其他编译器产生更有效的代码。
这就是为什么gcc和clang给出不同结果的原因。您已经编写了依赖于评估顺序的代码。这都不是任何编译器的错-我们不应该简单地编写依赖于行为不当的程序。如果必须按一定顺序执行这些功能,则应将它们分成几行/多个表达式。
对于逗号运算符,这是罕见的特殊情况之一。它带有一个内置的“序列点”,该序列点确保始终在对左操作数进行求值(执行)之前将其赋值。其他此类特殊情况是&& ||
运算符和?:
运算符。