理解gcc编译器上c代码的输出

时间:2015-08-12 02:22:28

标签: c gcc output

我从像What are all the common undefined behaviours that a C++ programmer should know about?这样的帖子中了解到,行为未定义并留给编译器。

但对于给定的代码,输出为2 1 3(始终如一)

#include<stdio.h>
int main(){
    int i = 1;
    printf("%d %d %d\n", i++, i++, i);
    return 0;
}

所以,我想知道,gcc遵循的顺序是什么,看起来不是从左到右或从右到左?输出总是一样的,这个问题只与gcc 4.9.1编译器有关。

4 个答案:

答案 0 :(得分:3)

您指定了gcc,因此我将陈述非常gcc具体的内容。

这是未定义的,但如果你考虑gcc如何实现调用堆栈,你就会知道为什么它是一致的。基本上,堆栈从高地址增长到低地址,并且在其参数列表中具有printf的{​​{1}}之类的函数会将参数转储到堆栈中。给定堆栈的方向,没有太多的选择:

...

从左向右推:

func(a1, a2, a3, a4);

或从右到左:

high| ... |
    | a1  |
    | a2  |
    | a3  |
low | a4  |

事实是,high| ... | | a4 | | a3 | | a2 | low | a1 | 采取第二种方法。这可能听起来有点反直觉,但是考虑到这样的堆栈结构,这是非常精心设计的。如果你必须按顺序访问参数,你自然会以这种方式形成一个数组,因此gccarr[0]等中的每一个都将对应一个正确的位置参数。

所以在了解了这一点之后,评估顺序不再难以理解,因为arr[1]以任何方式从右到左处理参数。如果我需要实现编译器处理函数调用,为什么我会选择在这种情况下从左到右处理参数,在这种情况下从右到左?这不仅令人困惑。

如果你知道它的一些实现,

gcc是一个非常全面的编译器。技术决策通常会带来非常明显的后果。

这是非常gcc特定的,所以如果有人改变堆栈的增长方向或者只是想创建特殊情况,事情可能会轻易改变。例如,gcc似乎表现得相反。

答案 1 :(得分:1)

未定义的行为意味着一个编译器可能以不同于另一个编译器的方式编译它。这是因为没有标准

关于gcc 4.9.1编译器,它评估中间的,然后是左边的,然后是右边的。它像兔子一样跳来跳去。在一般情况下,它可能从中间向左移动到右边。

答案 2 :(得分:1)

GCC doesn't appear to have an extensive and comprehensive database or documentation concerning undefined behavior - 如果有的话,通常可以理解为不冒险进入未定义的领域。但是,它确实记录了实现定义的行为。

要明确的是,我不认为i = i++问题可以在同一编译器的平台和实现之间进行最佳标准化,因为调用和计算的语义不同体系结构:this article可能与建筑物如何具有特殊的调用约定有关或有助于澄清。

此外,即使是C ++标准也允许一些&#34;编译室&#34;允许他们通过对程序员施加限制来以这种方式进行优化,尽管它们通常非常小且深奥,但可能很重要,例如strict aliasing

无论你如何将它们放入表达式中,固有碰撞的单个表达式的执行顺序可能适合于#34;编译器空间&#34;通常假设编译器完全控制它。

另外,由于GCC代码库的年龄和数量庞大,我怀疑这种文档是否已经存在。

**编辑:用户HuStmpHrrr似乎可以处理GCC的实施方式。看起来它需要一些挖掘!

答案 3 :(得分:0)

你可以要求GCC用:

生成汇编代码
gcc -S -o my_asm_output.s main.c

我明白了:

call    ___main
movl    $1, 28(%esp)
movl    28(%esp), %edx
leal    1(%edx), %eax
movl    %eax, 28(%esp)
movl    28(%esp), %eax
leal    1(%eax), %ecx
movl    %ecx, 28(%esp)
movl    28(%esp), %ecx
movl    %ecx, 12(%esp)
movl    %edx, 8(%esp)
movl    %eax, 4(%esp)
movl    $LC0, (%esp)
call    _printf

其中LC0:

LC0:
    .ascii "%d %d %d\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef

我不是汇编专家,但看起来打印顺序为printf("eax edx ecx\n");,对应增量:

movl    $1, 28(%esp)
movl    28(%esp), %edx
leal    1(%edx), %eax
leal    1(%eax), %ecx

但这只是一个快速的推测,也许我会跳过一些重要的事情。