【问题标题】:Using PUSH or MOV to pass arguments to a function [duplicate]使用 PUSH 或 MOV 将参数传递给函数 [重复]
【发布时间】:2016-08-30 16:40:50
【问题描述】:

我一直在反汇编一些可执行文件来学习汇编语言。我用 GCC 和 Visual Studio 编译了一个非常简单的程序,并注意到在传递参数方面有一个奇怪的区别。

(cdecl)int some_function(int, int)

VS:

mov eax, [ebp+8]
push eax
mov ecx, [ebp+4]
push ecx
call some_function

海合会:

mov eax, [ebp+8]
mov [esp+4], eax
mov eax, [ebp+4]
mov [esp], eax
call some_function

为什么 GCC 使用 mov 而不是 push

编辑:这是原始程序供参考。

int some_function(int a, int b) {
    return a + b;
}

int main(void) {
    int a = 1, b = 2;

    printf("string %d\n", some_function(a, b));
}

【问题讨论】:

  • 如果你不开启优化你会得到很多噪音(没有优化你通常会看到很多额外的加载和存储)。在没有看到其余的汇编代码的情况下,mov 的使用可能与在调用之前保持某种对齐有关。很难说。即使看到您编写的原始简单程序也可能会有所帮助。
  • 我已经添加了原程序的源代码。
  • 我认为这里的重点是试图了解这些负载和存储。随着优化,这个问题和理解可能会丢失。
  • 我不同意,你可以告诉编译器在做优化的时候不要内联。一旦你摆脱了噪音,你可以看看剩下的东西。在查看 GCC 代码之后,很明显他们不使用 push 的原因是因为代码试图在调用 some_function 之前保证堆栈是 16 字节对齐的。它们预先分配空间(包括调用参数空间),以便 esp 在调用之前保持 16 字节对齐。使用push 会再次错位。 GCC 的 32 位 ABI 即使在 Windows 上也需要这种对齐方式。
  • GCC 使用的 32 位 ABI 的描述可以在 here 找到。特别是 Section 2.2.2 The Stack Frame“输入参数区域的末尾应对齐在 16(32,如果 __m256 在堆栈上传递)字节边界。换句话说,当控制转移到函数入口点时,值 (%esp + 4) 始终是 16 (32) 的倍数。"

标签: assembly x86


【解决方案1】:

因为gcc这个版本在堆栈上为局部变量和要在函数序言中传递给被调用者的参数分配内存。

Visual C 编译器只对局部变量这样做,而是将被调用者的参数推入堆栈。

这是一种依赖于语言实现的行为。 gcc 的方法允许在评估被调用者的参数时进行更好的优化:编译器可能会重新排序评估,而不会记住尚未分配特定被调用者参数的空间。

【讨论】:

    【解决方案2】:

    一些编译器使用esp 作为基指针。
    如果使用push esp 会改变。不断变化的基指针会使它的使用复杂化;尤其是在循环中。

    通过使用mov [esp+x],reg 而不是push,编译器仍然可以“推送”参数,同时保持esp 作为基指针可用。

    不过,这是有代价的。现代 CPU 有一个堆栈引擎。
    通过不使用push,编译器绕过了堆栈引擎及其好处。

    【讨论】:

      猜你喜欢
      • 2022-01-26
      • 1970-01-01
      • 2013-07-09
      • 2012-11-13
      • 1970-01-01
      • 1970-01-01
      • 2016-10-09
      • 2015-11-10
      • 2015-06-06
      相关资源
      最近更新 更多