C/C ++在幕后按值返回结构 [英] C/C++ returning struct by value under the hood
问题描述
(此问题特定于我的计算机的体系结构和调用约定,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屋!