return 对象存储在哪里?
Where is the return object stored?
我大致了解函数 return 如何按值生成对象。但我想在较低的层次上理解它。组装水平是否合理。
我理解这段代码
ClassA fun(){
ClassA a;
a.set(...);
return a;
}
内部转换为
void fun(Class& ret){
ClassA a;
a.set(...);
ret.ClassA::ClassA(a);
}
这有效地调用了 return 值上的复制构造函数。
我也知道有一些优化(如 NRVO)可以生成以下代码,避免复制构造函数。
void fun(Class& ret){
ret.set(...);
}
不过我的问题有点基础。它实际上与特定对象无关。它甚至可以是原始类型。
假设我们有这段代码:
int fun(){
return 0;
}
int main(){
fun();
}
我的问题是 return 对象存储在内存中的什么地方。
如果我们看栈...有main
的栈帧,然后是fun
的栈帧。 return 对象是否存储在某个地址中,比如可能在两个堆栈帧之间?或者它可能存储在 main
堆栈帧中的某个位置(并且可能是在生成的代码中通过引用传递的地址)。
我考虑了一下,第二个似乎更实用,但是我不明白编译器如何知道要在 main
的堆栈帧中压入多少内存?它是否会计算最大的 return 类型并推送它,即使可能会浪费一些内存?或者它是动态完成的,它仅在调用函数之前分配 space?
在下面的代码中:
int fun()
{
return 0;
}
return 值存储在寄存器中。在英特尔架构上,这通常是 ax
(16 位),或 eax
(32 位),或 rax
(64 位)。 (历史上称为累加器。)
如果 return 值是指针或对对象的引用,它仍会 return 通过该寄存器编辑。
如果 return 值大于机器字,则 ABI(应用程序二进制接口)可能需要使用另一个寄存器来保存 high-order 字。因此,如果您在 16 位架构上 returning 一个 32 位数量,将使用 dx:ax
。 (对于更大架构中的更大数量,依此类推。)
较大的 return 值通过其他方式传递,例如您已经知道的 void fun(Class& ret)
机制。
通过累加器寄存器传递 return 值非常有效,这是一个有点强的约定,我见过的几乎所有 ABI 都需要它。
C++ 语言规范没有指定这些底层细节。它们由每个 C++ 实现指定,实际实现细节因平台而异。
几乎在每种情况下,return 值都是一个简单的本机类型,在某个指定的 CPU 寄存器中得到 returned。当一个函数 return 是一个 class 实例时,细节因实现而异。有几种常见的方法,但典型的情况是调用者负责为堆栈上的 return 值分配足够的 space,然后再调用函数并将额外的隐藏参数传递给函数,其中函数将复制 returned 值(或构造它,在 RVO 的情况下)。或者,参数是隐式的,函数可以在堆栈上找到 space,对于 return 值本身,在调用的堆栈帧之后。
给定的 C++ 实现也有可能仍然使用 CPU 寄存器到 return classes,它们足够小以适合单个 CPU 寄存器.或者,也许,一些 CPU 寄存器被保留用于 return 稍大的 classes。
细节各不相同,您需要查阅 C++ 编译器或操作系统的文档,以确定适用于您的具体细节。
答案是 ABI 特定的,但通常调用是使用隐藏参数编译的,该参数是指向函数应该使用的内存的指针,就像你说的那样假设函数被编译为
void fun(Class& ret){
ClassA a;
a.set(...);
ret.ClassA::ClassA(a);
}
然后在呼叫站点你会得到类似
的东西
Class instance = fun();
fun(instance);
现在这使得调用者在堆栈上保留 sizeof(Class)
字节并将该地址传递给函数,以便 fun
可以 "fill" space.
这与调用者的堆栈帧为它自己的局部变量保留 space 的方式没有什么不同,唯一的区别是它的一个局部变量的地址被传递给了 fun
。
请注意,如果 sizeof(Class)
小于一个寄存器(或几个寄存器)的大小,则完全有可能直接在其中返回值。
我大致了解函数 return 如何按值生成对象。但我想在较低的层次上理解它。组装水平是否合理。
我理解这段代码
ClassA fun(){
ClassA a;
a.set(...);
return a;
}
内部转换为
void fun(Class& ret){
ClassA a;
a.set(...);
ret.ClassA::ClassA(a);
}
这有效地调用了 return 值上的复制构造函数。
我也知道有一些优化(如 NRVO)可以生成以下代码,避免复制构造函数。
void fun(Class& ret){
ret.set(...);
}
不过我的问题有点基础。它实际上与特定对象无关。它甚至可以是原始类型。
假设我们有这段代码:
int fun(){
return 0;
}
int main(){
fun();
}
我的问题是 return 对象存储在内存中的什么地方。
如果我们看栈...有main
的栈帧,然后是fun
的栈帧。 return 对象是否存储在某个地址中,比如可能在两个堆栈帧之间?或者它可能存储在 main
堆栈帧中的某个位置(并且可能是在生成的代码中通过引用传递的地址)。
我考虑了一下,第二个似乎更实用,但是我不明白编译器如何知道要在 main
的堆栈帧中压入多少内存?它是否会计算最大的 return 类型并推送它,即使可能会浪费一些内存?或者它是动态完成的,它仅在调用函数之前分配 space?
在下面的代码中:
int fun()
{
return 0;
}
return 值存储在寄存器中。在英特尔架构上,这通常是 ax
(16 位),或 eax
(32 位),或 rax
(64 位)。 (历史上称为累加器。)
如果 return 值是指针或对对象的引用,它仍会 return 通过该寄存器编辑。
如果 return 值大于机器字,则 ABI(应用程序二进制接口)可能需要使用另一个寄存器来保存 high-order 字。因此,如果您在 16 位架构上 returning 一个 32 位数量,将使用 dx:ax
。 (对于更大架构中的更大数量,依此类推。)
较大的 return 值通过其他方式传递,例如您已经知道的 void fun(Class& ret)
机制。
通过累加器寄存器传递 return 值非常有效,这是一个有点强的约定,我见过的几乎所有 ABI 都需要它。
C++ 语言规范没有指定这些底层细节。它们由每个 C++ 实现指定,实际实现细节因平台而异。
几乎在每种情况下,return 值都是一个简单的本机类型,在某个指定的 CPU 寄存器中得到 returned。当一个函数 return 是一个 class 实例时,细节因实现而异。有几种常见的方法,但典型的情况是调用者负责为堆栈上的 return 值分配足够的 space,然后再调用函数并将额外的隐藏参数传递给函数,其中函数将复制 returned 值(或构造它,在 RVO 的情况下)。或者,参数是隐式的,函数可以在堆栈上找到 space,对于 return 值本身,在调用的堆栈帧之后。
给定的 C++ 实现也有可能仍然使用 CPU 寄存器到 return classes,它们足够小以适合单个 CPU 寄存器.或者,也许,一些 CPU 寄存器被保留用于 return 稍大的 classes。
细节各不相同,您需要查阅 C++ 编译器或操作系统的文档,以确定适用于您的具体细节。
答案是 ABI 特定的,但通常调用是使用隐藏参数编译的,该参数是指向函数应该使用的内存的指针,就像你说的那样假设函数被编译为
void fun(Class& ret){
ClassA a;
a.set(...);
ret.ClassA::ClassA(a);
}
然后在呼叫站点你会得到类似
的东西Class instance = fun();
fun(instance);
现在这使得调用者在堆栈上保留 sizeof(Class)
字节并将该地址传递给函数,以便 fun
可以 "fill" space.
这与调用者的堆栈帧为它自己的局部变量保留 space 的方式没有什么不同,唯一的区别是它的一个局部变量的地址被传递给了 fun
。
请注意,如果 sizeof(Class)
小于一个寄存器(或几个寄存器)的大小,则完全有可能直接在其中返回值。