问题描述
我试图弄清楚如果你从函数中按值返回一个结构体,而不是返回一个指向该结构体的指针,那么在 C++ 中实际发生了什么。如果一个函数只能返回一个可以放入寄存器的值,那么在按值发送结构时如何进行通信? (我在某处读到过。)
我尝试在 Godbolt 上测试它,看看它在做什么。但我不了解大会,所以这对我有点乐观。
在我看来,在没有太多汇编知识的情况下,该函数只是更改了调用该函数之前存在的一些内存?那么从函数中return
的概念只是一个抽象,函数只是在已经存在的内存位置设置一些字节,然后跳回到main()
?在这种情况下,根本不复制任何内容,返回值是
“免费”?
Godbolt: Return struct{int int int}
解决方法
如果一个函数只能返回一个可以放入寄存器的值,那么当它通过值发送时,结构如何通信?
一个函数可以返回任何合法的返回值。但是,由于显而易见的原因,按照将值留在单个寄存器中的约定,只能使用寄存器大小或更小的值来实现 return
语句。一些实现允许使用多个寄存器来表示大数据类型;当然,这意味着调用者必须被写入期望检查多个寄存器以获得完整的返回值。
机器级别的“发生了什么”不是语言标准规定的,取决于特定的编译器、它的优化能力、架构的细节等。但是,直接实现普通平台是让调用者在堆栈上保留空间(以便它在清理之后持续)并让被调用者在那里写入数据。由于分配是静态的,通常在计算调用者的堆栈帧大小时可以简单地考虑所需的空间。该实现可能会默默地生成一个指针并将其传递给寄存器中的被调用者;或者它可能会安排每个调用者将此保留空间放在其堆栈帧中的相同位置,以便被调用者可以向堆栈指针添加偏移量以确定写入位置;或者它可能会做一些我目前没有足够创意想到的其他事情。
有许多方法可以在机器级别处理函数之间的信息通信,这取决于机器和语言(尽管我们在进行这些讨论时通常谈论的是 C 或 C++,因为所有其他流行的选择要么在 VM 上运行,要么被解释,要么有其他一些奇特的事情发生)。您要研究的通用术语是应用程序二进制接口,或 ABI。
,所以我花了几个小时玩 Godbolt 的编译器资源管理器并阅读,直到我找到实用的答案。
我收集到的是:
- 如果该值适合寄存器,则将其作为返回值留在寄存器中。
- 如果该值适合 2 个寄存器,则保留在 2 个寄存器中。
- 如果值大于这个值,调用者会在自己的堆栈中保留内存,函数直接写入调用者的堆栈。
G++ 和 Clang 都做同样的事情,这是在 x86_64 上测试的。