【问题标题】:Why calling printf result in a different function prologue for main?为什么调用 printf 会导致 main 的函数序言不同?
【发布时间】:2015-10-01 20:28:18
【问题描述】:

当我编译时

#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

【问题讨论】:

  • 如果不进行优化,编译器会产生大量垃圾。不仅仅是你的例子,双 subl 是对这种不良行为的一个很好的暗示。
  • 这里有一个很好的解释:stackoverflow.com/questions/4228261/…

标签: c linux gcc assembly x86


【解决方案1】:

如果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 在 cmets 中所说,不要期望来自 -O0 的代码会被优化。使用-Og 进行不干扰可调试性的优化。 gcc 手册建议用于编译/调试/编辑周期。 -O0 输出比优化代码更难阅读/理解/学习。映射回源代码更容易,但代码很糟糕。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-30
    • 1970-01-01
    • 1970-01-01
    • 2015-04-29
    • 2018-12-31
    • 2021-11-06
    相关资源
    最近更新 更多