繁体   English   中英

本汇编代码中的“2 + 2”在哪里(由C中的gcc翻译)

[英]Where is the “2+2” in this Assembly code (translated by gcc from C)

我写了这个简单的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是变量,那么它们在这种情况下会达到什么值?

你得到的几乎所有代码都是无用的堆栈操作。 通过优化( 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来自编译器进行加法本身,而不是使生成的程序执行它(这称为常量折叠 )。

也许更有趣的是,如果您将代码更改为

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之间的差异。)

编译器确定2+2 = 4并将其内联。 常量存储在第10行( $4 )。 要验证这一点,请将数学运算更改为2+3 ,您将看到$5

编辑:至于寄存器本身, %rsp是堆栈指针, %rbp是帧指针, %eax是通用寄存器

您的程序没有可观察的行为,这意味着在一般情况下,编译器可能根本不会为它生成任何机器代码,除了一些最小的启动包装指令,旨在确保将零返回到调用环境。 至少将变量声明为volatile 或者在评估后打印其值。 或者从main返回。

另请注意,在C语言中, 2 + 2有资格作为整数常量表达式 这意味着编译器不仅被允许 ,而且实际上需要 在编译时知道该表达式的结果。 考虑到这一点,当编译时知道最终值时(即使您完全禁用优化),期望编译器在运行时评估2 + 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

这使程序摆脱了痛苦。

编译器对其进行了优化,它预先计算了答案并设置了结果。 如果你想看到编译器执行添加,那么你不能让它“看到”你正在提供的常量

如果您将此代码全部编译为对象(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);

添加两个数字的实际数学已经过优化。

我猜第10行,他因为所有都是常数而得到优化

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM