(这个问题特定于我的机器的架构和调用约定,Windows x86_64)
我不完全记得我在哪里读过这个,或者我是否正确地回忆过它,但是我听说过,当一个函数应该按值返回一些结构或对象时,它会将它填入{{1 (如果对象可以适合64位的寄存器宽度)或者在rax
中传递指向结果对象所在的指针(我猜在调用函数的堆栈帧中分配),在那里它会做所有通常的初始化,然后是rcx
回程。就是这样的事情
mov rax, rcx
确实会有像
这样的秘密参数extern some_struct create_it(); // implemented in assembly
我的记忆是正确的,还是我错了?如何通过函数值返回大对象(即比寄存器宽度宽)?
答案 0 :(得分:5)
这是完全正确的。调用者传递一个额外的参数,该参数是返回值的地址。通常它会在呼叫者的堆栈帧上,但没有保证。
精确的机制由平台ABI指定,但这种机制非常普遍。
各种评论员都为调用约定留下了有用的链接,因此我将其中的一些提升到这个答案:
关于x86 calling conventions的维基百科文章
Agner Fog的优化资源集合,包括summary of calling conventions(指向57-page PDF document的直接链接。)
calling conventions上的Microsoft Developer Network(MSDN)文档。
答案 1 :(得分:5)
这里简单地反汇编代码,例如你所说的
typedef struct
{
int b;
int c;
int d;
int e;
int f;
int g;
char x;
} A;
A foo(int b, int c)
{
A myA = {b, c, 5, 6, 7, 8, 10};
return myA;
}
int main()
{
A myA = foo(5,9);
return 0;
}
这里是foo函数的反汇编,以及调用它的主函数
主要强>
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 30h
call ___main
lea eax, [esp+20] ; placing the addr of myA in eax
mov dword ptr [esp+8], 9 ; param passing
mov dword ptr [esp+4], 5 ; param passing
mov [esp], eax ; passing myA addr as a param
call _foo
mov eax, 0
leave
retn
<强> FOO:强>
push ebp
mov ebp, esp
sub esp, 20h
mov eax, [ebp+12]
mov [ebp-28], eax
mov eax, [ebp+16]
mov [ebp-24], eax
mov dword ptr [ebp-20], 5
mov dword ptr [ebp-16], 6
mov dword ptr [ebp-12], 7
mov dword ptr [ebp-8], 9
mov byte ptr [ebp-4], 0Ah
mov eax, [ebp+8]
mov edx, [ebp-28]
mov [eax], edx
mov edx, [ebp-24]
mov [eax+4], edx
mov edx, [ebp-20]
mov [eax+8], edx
mov edx, [ebp-16]
mov [eax+0Ch], edx
mov edx, [ebp-12]
mov [eax+10h], edx
mov edx, [ebp-8]
mov [eax+14h], edx
mov edx, [ebp-4]
mov [eax+18h], edx
mov eax, [ebp+8]
leave
retn
现在让我们来看看刚刚发生的事情,所以当调用foo时,参数通过以下方式传递,9是最高地址,然后是5,然后是主要开始的myA地址
lea eax, [esp+20] ; placing the addr of myA in eax
mov dword ptr [esp+8], 9 ; param passing
mov dword ptr [esp+4], 5 ; param passing
mov [esp], eax ; passing myA addr as a param
在foo
内,有一些本地myA
存储在堆栈帧上,因为堆栈正在向下,myA
的最低地址从[ebp - 28]
开始, -28偏移可能是由结构对齐引起的,因此我猜测结构的大小应该是28字节,而不是预期的25。正如我们在创建foo
myA
foo
并在其中填充参数和立即值后myA
所见,它被复制并重新写入main
的地址从mov eax, [ebp+8]
mov edx, [ebp-28]
传递(这是按值返回的实际含义)
[ebp + 8]
main::myA
是main::myA
的地址存储(内存地址向上,因此ebp +旧ebp(4字节)+返回地址(4字节))在整体ebp + 8处到达foo::myA
的第一个字节,如前所述[ebp-28]
存储在mov [eax], edx
内,因为堆栈向下移动
foo::myA.b
将main::myA
放在main::myA.b
mov edx, [ebp-24]
mov [eax+4], edx
的第一个数据成员的地址中
foo::myA.c
将位于main::myA.b
地址的值放在edx中,并将该值放在main::myA.c
+ 4字节的地址mov edx, [ebp-20]
mov [eax+8], edx
mov edx, [ebp-16]
mov [eax+0Ch], edx
mov edx, [ebp-12]
mov [eax+10h], edx
mov edx, [ebp-8]
mov [eax+14h], edx
mov edx, [ebp-4]
mov [eax+18h], edx
mov eax, [ebp+8]
你可以看到这个过程通过函数
重复SELECT something, SOMETHING(something)
FROM
( SELECT something something FROM something
UNION ALL
SELECT soething FROM something
) x
GROUP
BY something;
基本上证明了当用val返回一个struct时,它不能作为一个param放置,会发生什么是返回值应该驻留的地址作为param传递给函数和函数内部被称为返回结构的值被复制到作为参数传递的地址...
希望这个例子可以帮助你更好地了解幕后发生的事情:)
修改强>
我希望您已经注意到我的示例使用的是32位汇编程序并且我知道您已经询问了x86-64,但我目前无法在64位机器上反汇编代码,所以我希望你能说明64位和32位的概念完全相同,并且调用约定几乎相同