【问题标题】:Assembler push rdi, pop rdi around function call汇编程序 push rdi, pop rdi around function call
【发布时间】:2013-02-03 21:36:47
【问题描述】:

在 C++ 中调用函数时 push rdi 和 pop rdi 的目的是什么? VS2010,x64,调试,无优化

C++

int calc()
{
    return 8 + 7;
}

反汇编:

int calc()
{
000000013F0B1020  push        rdi  
    return 8 + 7;
000000013F0B1022  mov         eax,0Fh  
}
000000013F0B1027  pop         rdi  
000000013F0B1028  ret 

【问题讨论】:

  • 会不会是某种“守卫”的东西?我想我在某处读到 MSVC always 在每个函数的顶部留有一些空间......
  • @KerrekSB,如果只是在函数代码的开头留一些“空格”,那么 NOOP 将是比无用的推送和弹出更明智的选择。

标签: c++ assembly 64-bit x86-64


【解决方案1】:

没有任何目的。这是未优化代码的常见工件。代码生成器发出push edi 指令以预期必须执行加法。必须在函数调用之间保留 EDI 寄存器。但后来,发现加法可以在编译时执行。

摆脱这样的无关代码需要"peephole optimization"。但是调试版本中没有启用该优化。要知道真正的代码是什么样子,您必须打开优化器,最好通过构建发布版本来完成。它实际上会完全消除该功能,您可以通过以下方式阻止它:

__declspec(noline) int calc()
{
    return 8 + 7;
}

在发布版本中产生:

    return 8 + 7;
000007F7038E1000  mov         eax,0Fh  
000007F7038E1005  ret  

【讨论】:

    【解决方案2】:

    你听说过“caller-save”和“callee-save”寄存器吗?

    由于您的 CPU 只有少量有限数量的寄存器,因此调用方/被调用函数通常不可能始终使用不同的寄存器。如果调用者函数和被调用函数都想使用同一个寄存器,则意味着调用者中的值必须在调用之前/之后保存/恢复。

    保存/恢复寄存器值可以由调用者或被调用者来完成——这是一个约定俗成的问题。 “调用者保存”寄存器的好处是,如果调用者知道调用后它不需要寄存器 XYZ 中的值,它可以省略保存/恢复操作。 “被调用者保存”寄存器的好处是,如果被调用者知道它不会修改寄存器 XYZ 中的值,它可以省略保存/恢复操作。

    我猜你的编译器将 RDI 视为一个被调用者保存寄存器,但不会省略不必要的保存/恢复操作,除非你打开了编译器优化。 (如果有人知道这是不正确的,请发布另一个答案!)

    更新:我发现了一篇关于 x86 调用约定的文章:http://en.wikipedia.org/wiki/X86_calling_conventions

    这似乎证实了大多数调用约定,RDI 将被调用者保存。这并不能解释为什么它没有推送和弹出所有其他被调用者保存寄存器。也许这里还有其他事情发生。

    【讨论】:

    • Re:“大多数”调用约定:主流的 x86-64 调用约定只有 2 种。在 Windows x64 中,RDI 和 RSI 是保留调用的。在 x86-64 System V 中,RDI 和 RSI 被调用破坏(并且是前 2 个 arg 传递寄存器)。 Unable to understand example of cdecl calling convention where caller doesnt need to clean the stack。大多数 32 位调用约定都有调用保留 EDI,但 32 位 x86 只有 7 个 GP 寄存器(不包括堆栈指针),所以如果排除 EBP,它几乎是易失性/非易失性之间的平均分配。
    猜你喜欢
    • 2022-11-22
    • 1970-01-01
    • 2015-09-04
    • 2023-04-01
    • 1970-01-01
    • 2015-02-17
    • 2012-12-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多