【发布时间】: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) 的倍数。"