【问题标题】:C Function Call Convention: Why movl instead of pushl?C 函数调用约定:为什么是 movl 而不是 pushl?
【发布时间】:2014-04-26 10:32:01
【问题描述】:

我不明白为什么以下行使用movl 将数据推送到堆栈指针下方是由 GCC 生成的。

movl    -4(%ebp), %eax      # -4(%ebp) <- local variable 1
movl    8(%ebp), %edx       # 8(%ebp)  <- first parameter
movl    %edx, 8(%esp)       # ??? WHY NOT:   pushl %edx
movl    %eax, 4(%esp)       # ??? WHY NOT:   pushl %eax
movl    -8(%ebp), %eax      # ??? WHY NOT:   pushl -8(%ebp)
movl    %eax, (%esp)
call    athena
movl    %eax, f

(full code)

我猜这段代码试图为函数调用推送 3 个参数。但是为什么不使用pushl。这段代码的用途是什么?它是如何工作的?

【问题讨论】:

  • 我不知道为什么会这样。也许是因为代码类似于通过寄存器传递参数的 x86-64? (例如,它们只有一个“将值加载到..”指令)。另外,你见过这个问题吗? stackoverflow.com/questions/22267767/…
  • PUSH 是一条遗留指令,在现代内核上执行不佳。它间接依赖于 ESP 寄存器的值。这使得乱序执行变得困难。 MOV没有这样的问题。如英特尔优化手册中所述,编码规则 25。
  • 这里有什么遗漏吗?我希望movels 之前的某个地方是sub %esp,xxx,其中xxx 是要传递的参数的大小?然后当电话返回时,不久之后,add %esp,xxx?
  • 还要注意它不是“低于堆栈指针”它实际上是在上面。 gcc 有控制这种行为的选项,参见手册中的-mpush-args-maccumulate-outgoing-args。他们也在那里给出了一些解释。

标签: assembly x86 32-bit disassembly gnu-assembler


【解决方案1】:

Hans Passant 回答正确。推送/弹出操作码可以分解为两个微操作,它们执行内存移动和堆栈指针的递增/递减。如果堆栈指针(或任何指针)被更新,然后立即用于下一个操作码,通常会发生执行停顿。通过堆栈指针访问各个内存位置(如您的示例中所示),不会出现停顿,并且操作可以配对,从而允许它们同时执行。

任何超标量 CPU 类型都将尝试在一个周期内执行多个操作码,如果它们的结果/源彼此无关。编译器正在为您做一些事情来加快执行速度,而手动执行这将是相当费力的。操作码可能比推送占用更多的空间,但它们的执行速度大约是两倍 - 所有其他事情都是一样的。

【讨论】:

    猜你喜欢
    • 2014-05-15
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    • 2015-06-29
    • 1970-01-01
    • 2014-05-10
    • 1970-01-01
    • 2011-08-21
    相关资源
    最近更新 更多