归档对象存储在哪里?

时间:2017-01-28 14:13:48

标签: c++ compiler-construction

我通常理解函数如何按值返回对象。但我想在较低的层面上理解它。装配水平,如果合理。

我理解这段代码

ClassA fun(){
    ClassA a;
    a.set(...);
    return a;
}

在内部转换为

void fun(Class& ret){
    ClassA a;
    a.set(...);
    ret.ClassA::ClassA(a);
}

有效地在返回值上调用复制构造函数。

我也理解有一些优化(如NRVO)可以生成以下代码,避免复制构造函数。

void fun(Class& ret){
    ret.set(...);
}

但是我的问题有点基础了。它与具体的对象无关。它甚至可以是原始类型。

假设我们有这段代码:

int fun(){
   return 0;
}
int main(){
    fun();
}

我的问题是存储在内存中的返回对象在哪里。

如果我们查看堆栈...有main的堆栈帧,然后是fun的堆栈帧。返回对象是否存储在某个地址中,可能在两个堆栈帧之间?或者它可能存储在main堆栈帧中的某个地方(可能是生成的代码中通过引用传递的地址)。

我已经考虑过了,第二个似乎更实用但是我不明白编译器如何知道在main的堆栈帧中推送多少内存?它是否计算出最大的返回类型是什么,并推动即使可能存在一些浪费的内存?或者它是动态完成的,它仅在调用函数之前分配该空间吗?

3 个答案:

答案 0 :(得分:14)

C ++语言规范未指定这些低级别详细信息。它们由每个C ++实现指定,实际的实现细节因平台而异。

几乎在所有情况下,返回值都是一个简单的本机类型,在某个指定的CPU寄存器中返回。当函数返回类实例时,细节会有所不同,具体取决于实现。有几种常见的方法,但典型的情况是调用者负责在调用函数并将其他隐藏参数传递给函数之前为堆栈上的返回值分配足够的空间,函数将复制到该函数返回值(或者在RVO的情况下构造它)。或者,参数是隐式的,并且函数可以在调用的堆栈帧之后在堆栈上找到返回值本身的空间。

给定的C ++实现仍然可以使用CPU寄存器来返回足够小的类以适合单个CPU寄存器。或者,可能会保留一些CPU寄存器来返回稍大的类。

详细信息各不相同,您需要查阅C ++编译器或操作系统的文档,以确定适用于您的具体详细信息。

答案 1 :(得分:7)

答案是ABI特定的,但通常调用是使用隐藏参数编译的,该参数是指向函数应该使用的内存的指针,就像你说的那样假设函数被编译为

void fun(Class& ret){
    ClassA a;
    a.set(...);
    ret.ClassA::ClassA(a);
}

然后在呼叫站点,您将有类似

的内容
Class instance = fun();
fun(instance);

现在这使得调用者在堆栈上保留sizeof(Class)个字节并将该地址传递给该函数,以便fun可以"填充"那个空间。

这与调用者的堆栈帧如何为其自己的本地人保留空间没有什么不同,唯一的区别是其本地之一的地址被传递给fun

请注意,如果sizeof(Class)小于寄存器(或几个寄存器)的大小,则完全有可能直接在其中返回值。

答案 2 :(得分:3)

在以下代码中:

int fun()
{
   return 0;
}

返回值存储在寄存器中。在intel架构上,这通常是ax(16位),或eax(32位)或rax(64位)。 (历史上称为累加器。)

如果返回值是指针或对象的引用,它仍将通过该寄存器返回。

如果返回值大于机器字,则ABI(应用程序二进制接口)可能需要使用另一个寄存器来保存高位字。因此,如果要在16位体系结构上返回32位数量,则将使用dx:ax。 (等等,对于更大的架构中的更大数量。)

更大的返回值通过其他方式传递,例如您已经知道的void fun(Class& ret)机制。

通过累加器寄存器传递返回值是非常有效的,这是一个有点强烈的约定,我见过的几乎所有ABI都需要它。