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

标签 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 中?

最佳答案

如果 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 输出比优化代码更难阅读/理解/学习。映射回源代码更容易,但这是糟糕的代码。

关于c - 为什么调用 printf 会导致 main 的不同函数序言?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32896305/

相关文章:

c - 如何在 Linux 中映射 Windows 共享 (LINUX) 下的文件?

node.js - 无法在 CentOS 7 上安装 node.js v8

linux - 如果我在 limits.conf 中为具有不同值的相同参数提供重复条目怎么办?

objective-c - GCC 编译器——错误或未指定的行为?

python - 无法安装 Orange : "error: command ' clang' failed with exit status 1"

c++ - 为什么在宏中使用明显无意义的 do-while 和 if-else 语句?

c - 为什么 fwrite 和 fread 打印垃圾?

java - 在我的 Swing GUI 中嵌入视频的简单方法

gcc - 将 NASM 和 64 位 C 代码编译并链接到引导加载程序中

c - C 函数无法为指针分配内存