想要查看某些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进行比较并采取适当的操作.但是大会上发生了什么?
因为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替换printf和fprintf使用puts和fputs分别在格式字符串不包含任何内容时,%并且没有传递其他参数.这是因为(有很多原因),它是便宜得多调用puts,并fputs在最后你还是得到想要打印的内容.