【问题标题】:Why Is GCC Using Mov Instead Of Push In Function Calls?为什么 GCC 使用 mov 而不是 push in 函数调用?
【发布时间】:2014-04-11 15:27:00
【问题描述】:

所以我有这个示例 C 程序。

int worship(long john)
{
    return 0 * john;
}

int main()
{
    return worship(666);
}

程序集看起来(基本上)是这样的:

worship(long):
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rdi, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $666, %edi
    call    worship(long)
    popq    %rbp
    ret

我在阅读堆栈粉碎时遇到了这个问题。在程序集worship(long): 部分,它说movq %rdi, -8(%rbp) 我希望它使用pushq,基于我到目前为止所读的所有内容。这是 GCC 将参数推送到堆栈上的新方式吗?如果是,我可以使用编译器标志来切换它吗?

【问题讨论】:

  • 哦,也许这是一种优化。据我所知 movq 不会改变堆栈指针的值
  • 在 x86_64 中寄存器的数量翻了一番,所以calling convention 使用寄存器作为第一个参数,而不是堆栈
  • 这是一个基本的代码优化。 PUSH 指令很笨拙,因为它进行两次 修改,它写入[ESP] 修改ESP 寄存器。这可以防止乱序执行,这是 MOV 没有的问题。
  • 如果您使用的是 Linux,那么前 6 个整数参数将在 RDI、RSI、RDX、RCX、R8 和 R9 中传递。所以这就是函数中有 RDI 的原因。传递 6 个参数,你会看到它使用了 R8 和 R9,这是新的寄存器之一。尝试传递超过 7 个参数,然后将使用堆栈

标签: c gcc assembly compilation stack


【解决方案1】:

像 GCC 这样的编译器是由那些非常仔细考虑如何使经常使用的代码 sn-ps(如函数调用/返回)尽可能高效的人编写的。当然,他们的解决方案针对的是一般情况,在特殊情况下可能会有更好的选择。

【讨论】:

    【解决方案2】:

    GCC manual 说,

    -mpush-args
    

    推送指令将用于在调用函数时传递传出参数。默认启用。

    -mno-push-args   
    

    使用 PUSH 操作来存储传出参数。这种方法更短,通常 与使用 SUB/MOV 操作的方法一样快,并且默认启用。 在某些情况下,禁用它可能会因为改进调度而提高性能 并减少依赖。

    -maccumulate-outgoing-args
    

    如果启用,传出参数所需的最大空间量将在函数序言中计算。这在大多数现代 CPU 上更快,因为当首选堆栈边界不等于 2 时,依赖减少、调度改进和堆栈使用减少。缺点是代码大小显着增加。此开关意味着 -mno-push-args。

    即使-mpush-args 默认启用,它也会被默认启用的-maccumulate-outgoing-args 覆盖。显式编译传递选项-mno-accumulate-outgoing-args 可以将指令更改为push

    【讨论】:

      猜你喜欢
      • 2020-07-07
      • 2019-10-30
      • 2012-10-18
      • 2014-09-14
      • 1970-01-01
      • 2016-12-27
      • 1970-01-01
      • 2011-08-21
      • 1970-01-01
      相关资源
      最近更新 更多