C / C ++在引擎盖下按值返回struct

时间:2016-08-21 20:19:10

标签: c++ c assembly x86-64 win64

(这个问题特定于我的机器的架构和调用约定,Windows x86_64)

我不完全记得我在哪里读过这个,或者我是否正确地回忆过它,但是我听说过,当一个函数应该按值返回一些结构或对象时,它会将它填入{{1 (如果对象可以适合64位的寄存器宽度)或者在rax中传递指向结果对象所在的指针(我猜在调用函数的堆栈帧中分配),在那里它会做所有通常的初始化,然后是rcx回程。就是这样的事情

mov rax, rcx

确实会有像

这样的秘密参数
extern some_struct create_it(); // implemented in assembly


我的记忆是正确的,还是我错了?如何通过函数值返回大对象(即比寄存器宽度宽)?

2 个答案:

答案 0 :(得分:5)

这是完全正确的。调用者传递一个额外的参数,该参数是返回值的地址。通常它会在呼叫者的堆栈帧上,但没有保证。

精确的机制由平台ABI指定,但这种机制非常普遍。

各种评论员都为调用约定留下了有用的链接,因此我将其中的一些提升到这个答案:

答案 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::myAmain::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位的概念完全相同,并且调用约定几乎相同