2016-03-05 64 views
-4

下面的代码在Code :: Blocks上编译和运行时显示不同的结果。初学者关于C程序的查询函数调用堆栈,序列点(排序)

void sum(int a,int b){ 
    printf("a=%d b=%d\n",a,b); 
} 
int main(){ 
    int i=1; 
    sum(i=5,++i); 
    printf("i=%d\n\n",i); 
    /***********************/ 
    i=2; 
    sum(i=5,i++); 
    printf("i=%d\n\n",i); 
    /**********************/ 
    i=3; 
    sum(i=5,i); 
    printf("i=%d\n\n",i); 
    return 0; 
} 

输出:

a=5 b=5 
i=5 

a=5 b=2 
i=5 

a=5 b=5 
i=5 

我认为这个问题的答案关系到序列点,并在此++运算符的序列点有关。 GCC必须按照命令传递值以固定顺序堆栈,但由于++的不同,答案不同。我觉得对于初学者来说这样写一个函数调用并不常见,但关于运算符的教训是一般的,所以可以尝试。

我的问题是,它的确切答案和像这样的问题应该是什么?在编译的哪个阶段,这些事情是决定的(明确或不清楚)?涉及哪种特定的算法(用于优化或一般)?相同的编译器能否为这种表达式或语句提供不同的结果最后一个问题是,初学者将如何理解和解决这些问题?有时非常令人惊讶。

+0

可能的重复[为什么这些构造(使用++)未定义的行为?](http://stackoverflow.com/questions/949433/why-are-these-constructs-using-undefined-behavior) – Olaf

+1

逗号在'sum(i = 5,++ i)中;'不是序列点,它分离函数参数。教训是,如果你想使用从单个变量派生的不同值,例如使用'sum(i + 5,i + 1)'并在之后更新'i'。或者在这种情况下,只需'sum(5,i + 1)'。除了别的,还不清楚你打算把“我”的最终价值是什么。是'5'还是'6'或'2'? –

+0

是的,这是更好的写作方式。 – skyconfusion

回答

1

操作的顺序是在编译的多个阶段决定的,这是导致您看到奇怪结果的原因。特别是在优化阶段,编译器可以以不常见的方式对代码进行重新排序,在这种情况下,它会影响结果(这很好,因为你正在做一些未定义的事情,并且编译器明确允许它做任何事情与该代码)。没有涉及任何具体算法,它是在不同点应用的几种不同算法之间的交互,并且在每个点应用的算法可以根据编译器决定的是处理特定位代码的最佳方式而变化。

当文档提到未定义的行为时,它不是未定义的特定编译器的行为,而是编译器必须或允许执行的规范。编译器的行为是完全定义的,但它是由深入分析器,代码生成器和优化器模块设计的详细决策来定义的,而且它非常复杂,即使编写编译器的开发人员也不会告诉你它会做什么,而无需花费大量的时间分析给定的代码如何流经整个过程。

初学者将无法弄清楚结果。即使是专家开发人员也可能无法。这就是为什么“未定义”对开发人员来说这样一个不受欢迎的词,为什么他们试图避免像鼠疫这样的不确定行为。引用讨论的语言规范,"In short, you can't use sizeof() on a structure whose elements haven't been defined, and if you do, demons may fly out of your nose."

+0

“编译器的行为已完全定义” - 这不是必需的。编译器可以很好地使用启发式的随机组件。链接(至少它的文本)意味着完全不同的东西。如果'struct'的大小不知道,就不可能计算它的大小。 – Olaf

+0

这是不可能计算的,但是因为试图做它的未定义行为的结果,编译器可以自由地做任何事情,从抛出一个错误到只是假定一些任意大小,并使用它来崩溃机器或更糟。抛出一个错误是因为计算的不可能性只是最常见的行为(因为大多数编译器开发者都是合理的,即使他们被允许也不会写出恶意行为)。 –