【问题标题】:Why asm generated by gcc mov twice?为什么由 gcc mov 生成 asm 两次?
【发布时间】:2017-03-01 21:55:33
【问题描述】:

假设我有以下 C 代码: #包括

int main()
{
    int x = 11;
    int y = x + 3;
    printf("%d\n", x);
    return 0;
}

然后我使用 gcc 将它编译成 asm,我得到了这个(去掉了一些标志):

main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $11, -4(%rbp)
    movl    -4(%rbp), %eax
    addl    $3, %eax
    movl    %eax, -8(%rbp)
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    leave
    ret

我的问题是为什么它是movl -4(%rbp), %eax 后跟movl %eax, %esi,而不是简单的movl -4(%rbp), %esi(根据我的实验效果很好)?

【问题讨论】:

  • 你启用优化了吗?
  • This is your code with -Os 选项...尝试将其更改为 -O0 看看会发生什么
  • @MarcGlisse 不,我没有。
  • 嗯,太棒了。通过优化,结果是巨大的。
  • gcc 做了两件事:从内存中读取 x,并将其存储在 ABI 规定的函数调用位置。如果没有优化,它不会尝试合并这两个步骤。

标签: c gcc assembly


【解决方案1】:

您可能没有启用优化。

如果不进行优化,编译器将生成这样的代码。一方面,它不将数据分配给寄存器,而是在堆栈上。这意味着当您对变量进行操作时,它们将首先被转移到寄存器中,然后再进行操作。

因此,鉴于x 的生活被分配在-4(%rbp) 中,这就是代码的外观,就好像您直接翻译它而不进行优化一样。首先将11 移动到x 的存储中。这意味着:

movl    $11, -4(%rbp)

完成第一个语句。下一条语句是评估x+3并放入y的存储中(即-8(%rbp),这是在不考虑之前生成的代码的情况下完成的:

movl     -4(%rbp), %eax
addl     $3, %eax
movl     %eax, -8(%rbp)

完成第二个语句。顺便说一下,分为两部分:x+3的评估和结果的存储。然后编译器继续为printf 语句生成代码,同样不考虑之前的语句。

另一方面,如果您启用优化,编译器会执行一些智能且对人类显而易见的事情。一件事是它允许将变量分配给寄存器,或者至少跟踪可以找到变量值的位置。在这种情况下,例如,编译器会在第二个语句中知道x 不仅存储在-4(%ebp),它还将知道它存储在$11(是的,它现在是实际值)。然后它可以使用它向它添加3,这意味着它知道结果是14(但它更聪明——它还看到你没有使用那个变量,所以它完全跳过了那个语句)。下一个语句是printf 语句,在这里它可以使用它知道x 是11 的事实并将其直接传递给printf。顺便说一句,它还意识到它无法在-4(%ebp) 使用x 的存储空间。最后它可能知道printf 做了什么(因为您包含stdio.h),因此可以分析格式字符串并在编译时进行转换,以将printf 语句替换为直接将14 写入标准输出的调用。

【讨论】:

  • 很好的解释。但是我想知道的是,为什么 gcc 先将-4(%rbp) 移动到%eax,然后再移动%eax, %esi,,而不是直接将mov 从-4(%rbp) 移动到%esi?
  • @Richard 乍一看可能有点奇怪,但它是关于编译器如何分解表达式并一次生成一段代码。首先它评估x,然后将结果放在适当的位置以供参数传递。 mov -4(%rbp), %eax 是对x 的求值(其中表达式的结果放在%eax 中),然后将结果放在适当的位置以进行参数传递,在本例中为%esi。
猜你喜欢
  • 1970-01-01
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 2011-11-03
相关资源
最近更新 更多