C/ c++在底层按值返回结构体
C/C++ returning struct by value under the hood
(这个问题特定于我的机器的架构和调用约定,Windows x86_64)
我完全不记得我读过这个,或者如果我回忆它正确,但我曾听说,当一个函数应该返回一些结构或对象的值,它要么东西在rax
(如果该对象可以在64位的寄存器的宽度)或被传递到生成的对象的指针会(我猜分配在调用函数的堆栈帧)在rcx
,哪里会做所有常见的初始化,然后mov rax, rcx
回程。也就是像
extern some_struct create_it(); // implemented in assembly
会有一个像
这样的秘密参数extern some_struct create_it(some_struct* secret_param_pointing_to_where_i_will_be);
我的记忆是正确的,还是我错了?如何从函数的值返回大对象(即比寄存器宽度宽)?
下面是一个简单的代码反汇编示例
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函数的反汇编,以及调用它的main函数
主: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开始的地址
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
的地址中(这就是按值返回的实际含义)
mov eax, [ebp+8]
mov edx, [ebp-28]
[ebp + 8]
是main::myA
的地址存储的地方(内存地址向上,因此ebp +旧ebp(4字节)+返回地址(4字节))在总体ebp + 8得到main::myA
的第一个字节,如前所述,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
地址中的值放在edx中,并将该值放在main::myA.b
+ 4字节的地址中,即main::myA.c
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返回一个结构体时,它不能作为参数放置,所发生的是返回值应该驻留的地址作为参数传递给函数,并且在被调用的函数中,返回结构体的值被复制到作为参数传递的地址中…
希望这个例子能帮助你更好地理解下面发生的事情:)
编辑
我希望你已经注意到我的例子是使用32位汇编器和我知道你已经问过关于x86-64,但我目前无法在64位机器上反汇编代码,所以我希望你相信我的话,这个概念对于64位和32位是完全相同的,调用约定几乎是相同的
完全正确。调用者传递一个额外的参数,该参数是返回值的地址。通常它会在调用者的堆栈帧上,但不能保证。
精确的机制是由平台ABI指定的,但这种机制是非常常见的。
许多评论者都留下了一些有用的调用约定文档链接,所以我将其中的一些提升到这个答案中:
-
Wikipedia关于x86调用约定的文章
-
Agner Fog的优化资源集合,包括调用约定的摘要(直接链接到57页的PDF文档)
-
Microsoft Developer Network (MSDN)调用约定文档。
-
StackOverflow x86标签wiki有很多有用的链接。
- 根据用户回答声明"Players"。用户选择玩家数量。播放器是结构体
- C++ 使用成员函数返回结构体指针属性的值
- 使用 boost::p ython,如何将结构体向量作为字典列表返回给 Python
- 如何使用其他函数返回的值为结构体成员赋值
- 使用 BOOST.python 从 C++ 返回结构体到 Python
- 如何使用结构体作为接口从函数返回指向数组的指针
- 如何从D DLL返回C结构体
- 调用返回结构体的c++ DLL函数
- 返回指向类定义的结构体的指针,多个作用域操作符
- 结构体的vector.size()将返回什么
- 类静态方法返回包含该类对象的结构体
- 按值返回结构体vs按引用返回结构体
- 当从固定结构体中存储的对象构建函数时,c++ lambda构建静态函数返回意外值
- 将返回值赋给结构体(c++)
- 从函数返回结构体时出现问题
- C/ c++在底层按值返回结构体
- 不能返回指向结构体的指针
- c++只赋值函数调用返回的结构体的一部分
- 如何从类库返回一个结构体数组到控制台应用程序
- c++从函数返回一个指向结构体的指针