解码C代码的等效汇编代码

puf*_*der 2 c assembly gcc

想要查看某些C代码的编译器输出(在汇编中),我在C中编写了一个简单的程序,并使用gcc生成了它的汇编文件.

代码是这样的:

#include <stdio.h>  

int main()  
{  
    int i = 0;

    if ( i == 0 )
    {
        printf("testing\n");
    }

    return 0;  
}  
Run Code Online (Sandbox Code Playgroud)

它的生成程序集在这里(只有主函数):

_main:  
pushl   %ebpz  
movl    %esp, %ebp  
subl    $24, %esp  
andl    $-16, %esp  
movl    $0, %eax  
addl    $15, %eax  
addl    $15, %eax  
shrl    $4, %eax  
sall    $4, %eax  
movl    %eax, -8(%ebp)  
movl    -8(%ebp), %eax  
call    __alloca  
call    ___main  
movl    $0, -4(%ebp)  
cmpl    $0, -4(%ebp)  
jne L2  
movl    $LC0, (%esp)  
call    _printf  
L2:  
movl    $0, %eax  
leave  
ret  
Run Code Online (Sandbox Code Playgroud)

我绝对不知道如何关联C代码和汇编代码.代码所要做的就是将0存储在寄存器中并将其与常量0进行比较并采取适当的操作.但是大会上发生了什么?

nat*_*ose 6

因为main很特别,你可以通过在另一个函数中执行这种类型的事情来获得更好的结果(最好是在它自己的文件中没有main).例如:

void foo(int x) {
    if (x == 0) {
       printf("testing\n");
    }
}
Run Code Online (Sandbox Code Playgroud)

装配可能会更清楚.这样做还允许您使用优化进行编译,并仍然可以观察条件行为.如果您要使用高于0的任何优化级别编译原始程序,它可能会取消比较,因为编译器可以继续并计算结果.使用此代码部分比较对编译器(在参数中x)是隐藏的,因此编译器无法进行此优化.

多余的东西实际上是什么

_main:  
pushl   %ebpz  
movl    %esp, %ebp  
subl    $24, %esp  
andl    $-16, %esp
Run Code Online (Sandbox Code Playgroud)

这是为当前功能设置堆栈帧.在x86中,堆栈帧是堆栈指针的值(SP,ESP或16位,32位或64位的RSP)与基指针的值(BP,EBP或RBP)之间的区域.这应该是局部变量存在的地方,但不是真的,并且在大多数情况下显式堆栈帧是可选的.但是,使用alloca和/或可变长度阵列将需要它们的使用.

这种特定的堆栈帧结构与非main函数不同,因为它还确保堆栈是16字节对齐的.ESP的减法会使堆栈大小增加到足以容纳局部变量,并andl从中有效地减去0到15,使其与16字节对齐.这种对齐似乎过多,除了它会强制堆栈也开始缓存对齐以及字对齐.

movl    $0, %eax  
addl    $15, %eax  
addl    $15, %eax  
shrl    $4, %eax  
sall    $4, %eax  
movl    %eax, -8(%ebp)  
movl    -8(%ebp), %eax  
call    __alloca  
call    ___main 
Run Code Online (Sandbox Code Playgroud)

我不知道这一切是怎么回事. alloca通过更改堆栈指针的值来增加堆栈帧大小.

movl    $0, -4(%ebp)  
cmpl    $0, -4(%ebp)  
jne L2  
movl    $LC0, (%esp)  
call    _printf  
L2:  
movl    $0, %eax  
Run Code Online (Sandbox Code Playgroud)

我想你知道这是做什么的.如果没有,那movl就是call将字符串的地址移动到堆栈的顶部位置,以便可以通过printf进行重试.它必须在堆栈上传递,以便printf可以使用它的地址来推断printf的其他参数的地址(如果有的话,在这种情况下不存在).

leave  
Run Code Online (Sandbox Code Playgroud)

该指令删除前面谈到的堆栈帧.它基本上movl %ebp, %esp紧随其后popl %ebp.还有一个enter可用于构造堆栈帧的指令,但gcc没有使用它.当未明确使用堆栈帧时,EBP可以将其用作通用的puropose寄存器,而不是leave编译器只是将堆栈帧大小添加到堆栈指针,这将使堆栈大小减小帧大小.

ret
Run Code Online (Sandbox Code Playgroud)

我不需要解释这个.

使用优化进行编译时

我相信你会用不同的优化级别重新编译所有这些,所以我会指出可能会发生的事情,你可能会发现奇怪的事情.我已经观察到gcc替换printffprintf使用putsfputs分别在格式字符串不包含任何内容时,%并且没有传递其他参数.这是因为(有很多原因),它是便宜得多调用puts,并fputs在最后你还是得到想要打印的内容.