为什么GCC和Clang的结果与以下代码不同?

时间:2019-04-10 10:31:03

标签: c gcc llvm

对于以下使用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

3 个答案:

答案 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,我们不知道ab将首先执行。评估的顺序是不确定的-编译器可以按照自己喜欢的任何顺序执行它,不需要记录如何做,甚至不必因情况而异。

C标准有意将此保留为未指定,以允许不同的编译器以不同的方式解析表达式。从本质上讲,它们使他们的表达式树算法成为编译器的商业秘密,以使某些编译器在自由市场上能够比其他编译器产生更有效的代码。

这就是为什么gcc和clang给出不同结果的原因。您已经编写了依赖于评估顺序的代码。这都不是任何编译器的错-我们不应该简单地编写依赖于行为不当的程序。如果必须按一定顺序执行这些功能,则应将它们分成几行/多个表达式。

对于逗号运算符,这是罕见的特殊情况之一。它带有一个内置的“序列点”,该序列点确保始终在对左操作数进行求值(执行)之前将其赋值。其他此类特殊情况是&& ||运算符和?:运算符。