我写过这个简单的C代码
int main()
{
int calc = 2+2;
return 0;
}
我希望看到它在汇编中看起来如何,所以我使用gcc
$ gcc -S -o asm.s test.c
结果是大约65行(Mac OS X 10.8.3),我发现这些是相关的:
我在哪里查找此代码中的2+2
?
问题的一部分尚未得到解决。
如果%rbp, %rsp, %eax
是变量,那么它们在这种情况下会达到什么值?
答案 0 :(得分:11)
你得到的几乎所有代码都是无用的堆栈操作。通过优化(gcc -S -O2 test.c
),您将获得类似
main:
.LFB0:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
.LFE0:
忽略以点开头或以冒号结尾的每一行:只有两个汇编指令:
xorl %eax, %eax
ret
他们编码return 0;
。 (对一个寄存器进行异或,将其设置为全位为零。函数返回值按照x86 ABI进入寄存器%eax
。)与int calc = 2+2;
的所有操作都被丢弃为未使用。
如果您将代码更改为
int main(void) { return 2+2; }
你会得到
movl $4, %eax
ret
其中4来自编译器执行添加本身而不是使生成的程序执行此操作(这称为constant folding)。
如果将代码更改为
,也许更有趣int main(int argc, char **argv) { return argc + 2; }
然后你得到
leal 2(%rdi), %eax
ret
这是在运行时做一些真正的工作!在64位ELF ABI中,%rdi
保存函数的第一个参数,在这种情况下为argc
。 leal 2(%rdi), %eax
是“%eax = %edi + 2
”的x86汇编语言,并且这样做主要是因为更熟悉的add
指令只接受两个参数,所以你不能用它来添加2 %rdi
并将结果放在%eax
一条指令中。 (暂时忽略%rdi
和%edi
之间的差异。)
答案 1 :(得分:10)
编译器确定2+2 = 4
并将其内联。常量存储在第10行($4
)中。要验证这一点,请将数学运算更改为2+3
,您将看到$5
编辑:对于寄存器本身,%rsp
是堆栈指针,%rbp
是帧指针,%eax
是通用寄存器
答案 2 :(得分:2)
您的程序没有可观察的行为,这意味着在一般情况下,编译器可能根本不会为它生成任何机器代码,除了一些最小的启动包装指令,旨在确保将零返回到调用环境。至少将您的变量声明为volatile
。或者在评估后打印其值。或者从main
返回。
另请注意,在C语言中2 + 2
符合整数常量表达式。这意味着编译器不仅仅是允许的,而实际上需要在编译时知道该表达式的结果。考虑到这一点,期望编译器在运行时评估2 + 2
并在编译时知道最终值(即使您完全禁用优化)也是很奇怪。
答案 3 :(得分:2)
以下是汇编代码的说明:
pushq %rbp
这会将帧指针的副本保存在堆栈中。函数本身不需要这个;它就在那里,调试器或异常处理程序可以在堆栈上找到帧。
movq %rsp, %rbp
通过将帧指针设置为指向当前堆栈顶部来启动新帧。同样,该功能不需要这个;保持适当的堆叠是家务。
mov $4, -12(%rbp)
这里编译器将calc
初始化为4.这里发生了几件事。首先,编译器自己评估2+2
并在汇编代码中使用结果4。算术不在执行程序中执行;它是在编译器中完成的。其次,calc
已被指定为帧指针下方12个字节的位置。 (这很有趣,因为它也低于堆栈指针。此架构的OS X ABI包括允许程序使用的堆栈指针下方的“红色区域”,这是不寻常的。)第三,程序清晰地编译而没有优化。我们知道,因为优化器会认识到这段代码没有效果而且没用,所以它会删除它。
movl $0, -8(%rbp)
此代码在编译器预留的位置存储0以准备main
的返回值。
movl -8(%rbp), %eax
movl %eax, -4(%rbp)
这会将数据从准备返回值的位置复制到临时处理位置。这比以前的代码更无用,强化了没有使用优化的结论。这看起来像我期望的负优化级别的代码。
movl -4(%rbp), %eax
这会将返回值从临时处理位置移动到返回给调用者的寄存器。
popq %rbp
这会恢复帧指针,从而从堆栈中删除先前推送的帧。
ret
这使程序摆脱了痛苦。
答案 4 :(得分:1)
编译器对其进行了优化,它预先计算了答案并设置了结果。如果你想看到编译器执行添加,那么你不能让它“看到”你正在提供的常量
如果您将此代码全部编译为对象(gcc -O2 -c test_add.c -o test_add.o) 那么你将强制编译器生成添加代码。但操作数将是寄存器或堆栈。
int test_add ( int a, int b )
{
return(a+b);
}
然后,如果你从一个单独的源代码中调用它(gcc -O2 -c test.c -o test.o),那么你会看到两个操作数被强制进入函数。
extern int test_add ( int, int );
int test ( void )
{
return(test_add(2,2));
}
你可以反汇编这两个对象(objdump -D test.o,objdump -D test_add.o)
当您在一个文件中执行简单的操作时
int main ( void )
{
int a,b,c;
a=2;
b=2;
c=a+b;
return(0);
}
编译器可以将您的代码优化为几个等价物之一。我的例子在这里没有做任何事,数学和结果都没有用,它们没有被使用,所以它们可以简单地作为死代码被删除。你的opitmization做了这个
int main ( void )
{
int c;
c=4;
return(0);
}
但这也是对上述代码的完全有效的优化
int main ( void )
{
return(0);
}
编辑:
calc = 2 + 2?
在哪里我相信
movl $4,-12(%rbp)
是2 + 2(答案是计算出来的,只是放在堆栈中的calc中。
movl $0,-8(%rbp)
我假设你的回报是0(0);
添加两个数字的实际数学已经过优化。
答案 5 :(得分:0)
我猜第10行,因为所有都是常量
而被优化