C/C ++在幕后按值返回结构 [英] C/C++ returning struct by value under the hood

查看:106
本文介绍了C/C ++在幕后按值返回结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(此问题特定于我的计算机的体系结构和调用约定,Windows x86_64)

(This question is specific to my machine's architecture and calling conventions, Windows x86_64)

我不完全记得我在哪里读的书,或者我是否正确地记得它,但是我听说,当一个函数应该按值返回某些结构或对象时,它要么将其填充到rax中,要么将其填充到rax中. (如果对象可以适合64位寄存器的宽度),或者将指针传递给rcx中结果对象将要放置在哪里(我猜是在调用函数的堆栈帧中分配),它将在其中执行所有通常的初始化,然后返回mov rax, rcx.也就是说,类似

I don't exactly remember where I had read this, or if I had recalled it correctly, but I had heard that, when a function should return some struct or object by value, it will either stuff it in rax (if the object can fit in the register width of 64 bits) or be passed a pointer to where the resulting object would be (I'm guessing allocated in the calling function's stack frame) in rcx, where it would do all the usual initialization, and then a mov rax, rcx for the return trip. That is, something like

extern some_struct create_it(); // implemented in assembly

真的会有像这样的秘密参数

would really have a secret parameter like

extern some_struct create_it(some_struct* secret_param_pointing_to_where_i_will_be);


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


Did my memory serve me right, or am I incorrect? How are large objects (i.e. wider than the register width) returned by value from functions?

推荐答案

下面是一个简单的代码汇编,示例了您在说什么

Here's a simple disassembling of a code exampling what you're saying

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函数的反汇编,以及调用它的主要函数

and here's the disassembly of the foo function, and the main function calling it

主要:

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,然后main中的myA地址开始

now let's go through what just happened, so when calling foo the paramaters were passed in the following way, 9 was at highest address, then 5 then the address the myA in main begins

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中看到的那样,在创建foo的本地myA并填充参数和立即值之后,将其复制并重新写入从main传递的myA的地址(是按值返回的实际含义)

within foo there is some local myA which is stored on the stack frame, since the stack is going downwards, the lowest address of myA begins in [ebp - 28], the -28 offset could be caused by struct alignments so I'm guessing the size of the struct should be 28 bytes here and not 25 as expected. and as we can see in foo after the local myA of foo was created and filled with parameters and immediate values, it is copied and re-written to the address of myA passed from main ( this is the actual meaning of return by value )

mov     eax, [ebp+8]
mov     edx, [ebp-28]

[ebp + 8]是存储main::myA的地址的位置(内存地址向上,因此ebp +旧的ebp(4个字节)+返回地址(4个字节))以总ebp + 8到达第一个字节main::myA,如前所述,foo::myA随着堆栈向下而存储在[ebp-28]

[ebp + 8] is where the address of main::myA was stored ( memory address go upwards hence ebp + old ebp ( 4 bytes ) + return address ( 4 bytes )) at overall ebp + 8 to get to the first byte of main::myA, as said earlier foo::myA is stored within [ebp-28] as stack goes downwards

mov     [eax], edx     

foo::myA.b放置在main::myA的第一个数据成员的地址中,该地址是main::myA.b

place foo::myA.b in the address of the first data member of main::myA which is main::myA.b

mov     edx, [ebp-24]
mov     [eax+4], edx

将值放置在edx中的foo::myA.c地址中,并将该值放置在main::myA.b + 4个字节的地址内,即main::myA.c

place the value that resides in the address of foo::myA.c in edx, and place that value within the address of main::myA.b + 4 bytes which is main::myA.c

如您所见,此过程会在整个函数中重复进行

as you can see this process repeats itself through out the function

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]

基本上证明了当通过val返回一个不能作为参数放置的struct时,发生的事情是将返回值应位于的地址作为参数传递给函数和函数内被称为返回结构的值将被复制到作为参数传递的地址中.

which basically proves that when returning a struct by val, that could not be placed in as a param, what happens is that the address of where the return value should reside in is passed as a param to the function and within the function being called the values of the returned struct are copied into the address passed as a parameter...

希望这个示例可以帮助您更好地了解引擎盖下发生的事情:)

hope this exampled helped you visualize what happens under the hood a little bit better :)

编辑

我希望您注意到我的示例使用的是32位汇编器,并且您已经问过有关x86-64的我知道,但是我目前无法在64位上反汇编代码机器,所以希望您能相信我的观点:64位和32位的概念完全相同,并且调用约定几乎相同

I hope that you've noticed that my example was using 32 bit assembler and I KNOW you've asked regarding x86-64, but I'm currently unable to disassemble code on a 64 bit machine so I hope you take my word on it that the concept is exactly the same both for 64 bit and 32 bit, and that the calling convention is nearly the same

这篇关于C/C ++在幕后按值返回结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆