Assembly 使用PUSH或MOV向函数传递参数

Assembly 使用PUSH或MOV向函数传递参数,assembly,x86,Assembly,X86,我一直在分解一些可执行文件来学习汇编语言。我用GCC和visualstudio编译了一个非常简单的程序,并注意到在传递参数方面有一个奇怪的差异 (cdecl)int一些函数(int,int) VS: 通用条款: 为什么GCC使用mov而不是push 编辑:这是供参考的原始程序 int some_function(int a, int b) { return a + b; } int main(void) { int a = 1, b = 2; printf("stri

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

(cdecl)
int一些函数(int,int)

VS:

通用条款:

为什么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));
}
因为此版本的
gcc
在堆栈上为本地变量和要在函数序言中传递给被调用方的参数分配内存

visualc
编译器只对局部变量执行此操作,并将被调用方的参数推送到堆栈上

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

因为此版本的
gcc
在堆栈上为本地变量和要在函数序言中传递给被调用方的参数分配内存

visualc
编译器只对局部变量执行此操作,并将被调用方的参数推送到堆栈上


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

一些编译器使用
esp
作为基本指针。
如果使用
push
esp
将发生变化。更改基指针会使其使用复杂化;尤其是在循环中

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

但这是有代价的。现代CPU有一个堆栈引擎。

通过不使用
push
编译器绕过了堆栈引擎及其优点

一些编译器使用
esp
作为基本指针。
如果使用
push
esp
将发生变化。更改基指针会使其使用复杂化;尤其是在循环中

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

但这是有代价的。现代CPU有一个堆栈引擎。

通过不使用
push
编译器绕过了堆栈引擎及其优点

如果不启用优化(如果没有优化,通常会看到大量额外的加载和存储),您将收到大量噪音。如果看不到其余的汇编代码,则可能是使用
mov
与在调用之前保持某种对齐有关。很难说。即使看到您编写的原始简单程序也可能有所帮助。我已经为原始程序添加了源代码。我认为这里的重点是试图理解这些加载和存储。对于优化,这个问题和理解可能会丢失。我不同意,您可以告诉编译器在进行优化时不要内联。一旦你消除了噪音,你就可以看看剩下的东西了。查看GCC代码后,很明显,他们不使用push的原因是因为代码试图在调用
某些函数之前确保堆栈是16字节对齐的。它们预先分配空间(包括调用参数空间),以便
esp
在调用之前保持16字节对齐。使用
push
会再次使其错位。即使在Windows上,GCC的32位ABI也需要这样的对齐。可以找到GCC使用的32位ABI的描述。特别是在第2.2.2节中,堆栈帧表示“输入参数区域的末端应在16(32,如果在堆栈上传递了_m256)字节边界上对齐。换句话说,当控制转移到函数入口点时,值(%esp+4)始终是16(32)的倍数。”如果不启用优化(如果没有优化,通常会看到大量额外的加载和存储),您将收到大量噪音。如果看不到其余的汇编代码,则可能是使用
mov
与在调用之前保持某种对齐有关。很难说。即使看到您编写的原始简单程序也可能有所帮助。我已经为原始程序添加了源代码。我认为这里的重点是试图理解这些加载和存储。对于优化,这个问题和理解可能会丢失。我不同意,您可以告诉编译器在进行优化时不要内联。一旦你消除了噪音,你就可以看看剩下的东西了。查看GCC代码后,很明显,他们不使用push的原因是因为代码试图在调用
某些函数之前确保堆栈是16字节对齐的。它们预先分配空间(包括调用参数空间),以便
esp
在调用之前保持16字节对齐。使用
push
会再次使其错位。即使在Windows上,GCC的32位ABI也需要这样的对齐。可以找到GCC使用的32位ABI的描述。特别是在第2.2.2节中,堆栈帧表示“输入参数区域的末端应在16(32,如果在堆栈上传递了_m256)字节边界上对齐。换句话说,当控制转移到函数入口点时,值(%esp+4)始终是16(32)的倍数。”
mov eax, [ebp+8]
mov [esp+4], eax
mov eax, [ebp+4]
mov [esp], eax
call some_function
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));
}