为什么调用printf导致main的不同函数序言?

时间:2015-10-01 20:28:19

标签: c linux gcc assembly x86

编译时

#include <stdio.h>
int
main () {
    return 0;
}

到x86程序集,结果是明确的和预期的:

$> cc -m32 -S main.c -o -|sed -r "/\s*\./d"
main:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $0, %eax
    popl    %ebp
    ret

但是在研究不同的反汇编二进制文件时,函数序言从未如此简单。确实,将上面的C源改为

#include <stdio.h>
int
main () {
    printf("Hi");
    return 0;
}

结果是

$> cc -m32 -S main.c -o -|sed -r "/\s*\./d"
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $4, %esp
    subl    $12, %esp
    call    printf
    addl    $16, %esp
    movl    $0, %eax
    movl    -4(%ebp), %ecx
    leave
    leal    -4(%ecx), %esp
    ret

特别是,我不明白这些说明的原因

leal    4(%esp), %ecx
andl    $-16, %esp
pushl   -4(%ecx)
生成

- 特别是为什么不直接将%esp存储到%ecx而不是%esp+4

1 个答案:

答案 0 :(得分:4)

如果main不是叶子函数,它需要对齐堆栈以获得它调用的任何函数。不被称为main的函数只是保持堆栈的对齐。

lea 4(%esp), %ecx   # ecx = esp+4
andl    $-16, %esp
pushl   -4(%ecx)    # load from ecx-4 and push that

它正在推送一个返回地址的副本,因此在对齐堆栈后它将位于正确的位置。你是对的,不同的序列会更明智:

mov    (%esp), %ecx   ; or maybe even  pop %ecx
andl   $-16, %esp
push   %ecx           ; push (mem) is slower than push reg

正如Youka在评论中所说,不要期望-O0的代码在所有中优化。使用-Og进行不会干扰可调试性的优化。 gcc手册建议用于编译/调试/编辑循环。 -O0输出比优化代码更难阅读/理解/学习。它可以更容易地映射到源代码,但它的代码很糟糕。