为了了解这一点,我编写了这个简单的代码,其中我只是创建了不同类型的变量,并通过值、引用和指针将它们传递给函数:
int i = 1;
char c = 'a';
int* p = &i;
float f = 1.1;
TestClass tc; // has 2 private data members: int i = 1 and int j = 2
函数体留空,因为我只是在查看参数是如何传入的。
passByValue(i, c, p, f, tc);
passByReference(i, c, p, f, tc);
passByPointer(&i, &c, &p, &f, &tc);
想看看这对数组有何不同,以及如何访问参数。
int numbers[] = {1, 2, 3};
passArray(numbers);
组装:
passByValue(i, c, p, f, tc)
mov EAX, DWORD PTR [EBP - 16]
mov DL, BYTE PTR [EBP - 17]
mov ECX, DWORD PTR [EBP - 24]
movss XMM0, DWORD PTR [EBP - 28]
mov ESI, DWORD PTR [EBP - 40]
mov DWORD PTR [EBP - 48], ESI
mov ESI, DWORD PTR [EBP - 36]
mov DWORD PTR [EBP - 44], ESI
lea ESI, DWORD PTR [EBP - 48]
mov DWORD PTR [ESP], EAX
movsx EAX, DL
mov DWORD PTR [ESP + 4], EAX
mov DWORD PTR [ESP + 8], ECX
movss DWORD PTR [ESP + 12], XMM0
mov EAX, DWORD PTR [ESI]
mov DWORD PTR [ESP + 16], EAX
mov EAX, DWORD PTR [ESI + 4]
mov DWORD PTR [ESP + 20], EAX
call _Z11passByValueicPif9TestClass
passByReference(i, c, p, f, tc)
lea EAX, DWORD PTR [EBP - 16]
lea ECX, DWORD PTR [EBP - 17]
lea ESI, DWORD PTR [EBP - 24]
lea EDI, DWORD PTR [EBP - 28]
lea EBX, DWORD PTR [EBP - 40]
mov DWORD PTR [ESP], EAX
mov DWORD PTR [ESP + 4], ECX
mov DWORD PTR [ESP + 8], ESI
mov DWORD PTR [ESP + 12], EDI
mov DWORD PTR [ESP + 16], EBX
call _Z15passByReferenceRiRcRPiRfR9TestClass
passByPointer(&i, &c, &p, &f, &tc)
lea EAX, DWORD PTR [EBP - 16]
lea ECX, DWORD PTR [EBP - 17]
lea ESI, DWORD PTR [EBP - 24]
lea EDI, DWORD PTR [EBP - 28]
lea EBX, DWORD PTR [EBP - 40]
mov DWORD PTR [ESP], EAX
mov DWORD PTR [ESP + 4], ECX
mov DWORD PTR [ESP + 8], ESI
mov DWORD PTR [ESP + 12], EDI
mov DWORD PTR [ESP + 16], EBX
call _Z13passByPointerPiPcPS_PfP9TestClass
passArray(numbers)
mov EAX, .L_ZZ4mainE7numbers
mov DWORD PTR [EBP - 60], EAX
mov EAX, .L_ZZ4mainE7numbers+4
mov DWORD PTR [EBP - 56], EAX
mov EAX, .L_ZZ4mainE7numbers+8
mov DWORD PTR [EBP - 52], EAX
lea EAX, DWORD PTR [EBP - 60]
mov DWORD PTR [ESP], EAX
call _Z9passArrayPi
// parameter access
push EAX
mov EAX, DWORD PTR [ESP + 8]
mov DWORD PTR [ESP], EAX
pop EAX
我假设我正在查看与参数传递有关的正确程序集,因为每个程序的末尾都有调用!
但由于我对组装的了解非常有限,我无法判断这里发生了什么。我了解了 ccall 约定,所以我假设正在发生的事情与保留调用者保存的寄存器然后将参数插入堆栈有关。正因为如此,我期待看到加载到寄存器中的东西并“推送”到任何地方,但不知道 mov
s 和 lea
s 发生了什么。另外,我不知道 DWORD PTR
是什么。
我只了解了寄存器:eax、ebx、ecx、edx、esi、edi、esp
和 ebp
,所以看到类似 XMM0
或 DL
也让我感到困惑。我想在通过引用/指针传递时看到 lea
是有意义的,因为它们使用内存地址,但我实际上无法判断发生了什么。当谈到按值传递时,似乎有很多指令,所以这可能与将值复制到寄存器中有关。不知道数组是如何作为参数传递和访问的。
如果有人可以向我解释每个组装 block 的大致情况,我将不胜感激。
最佳答案
使用 CPU 寄存器传递参数比使用内存(即堆栈)更快。然而,CPU 中的寄存器数量有限(尤其是在 x86 兼容的 CPU 中),因此当函数有很多参数时,将使用堆栈而不是 CPU 寄存器。在您的情况下,有 5 个函数参数,因此编译器使用堆栈作为参数而不是寄存器。
原则上编译器可以使用 push
指令在实际 call
运行之前将参数推送到堆栈,但是许多编译器(包括 gnu c++)使用 mov
将参数推送到堆栈。这种方式很方便,因为它不会更改调用函数的代码部分中的 ESP 寄存器(堆栈顶部)。
在 passByValue(i, c, p, f, tc)
的情况下,参数的值被放置在堆栈上。您可以看到许多 mov
指令从内存位置到寄存器以及从寄存器到堆栈的适当位置。这样做的原因是 x86 程序集禁止从一个内存位置直接移动到另一个内存位置(movs
异常(exception),它将值从一个数组(或您希望的字符串)移动到另一个)。
在 passByReference(i, c, p, f, tc)
的情况下,您可以看到许多 5 lea 指令将参数的 地址 复制到 CPU 寄存器,这些寄存器的值被移入堆栈。
passByPointer(&i, &c, &p, &f, &tc)
的情况与passByValue(i, c, p, f, tc)
类似。在内部,在汇编级别,按引用传递使用指针,而在更高的 C++ 级别上,程序员不需要在引用上显式使用 &
和 *
运算符.
参数入栈后调用
,将指令指针EIP
压入栈,然后将程序执行转移到子程序。在 call
指令之后,所有参数到堆栈的 moves
说明了堆栈上即将到来的 EIP
。
关于c++ - 汇编如何做参数传递: by value,引用,不同类型/数组的指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19858980/