加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c – 是否会在堆上分配内存以支持临时对象与const引用的嵌套绑定

发布时间:2020-12-16 10:43:43 所属栏目:百科 来源:网络整理
导读:请考虑以下代码,它以“嵌套”方式将临时对象绑定到const引用: #include iostreamstd::string foo(){ return "abc";}std::string goo(){ const std::string a = foo(); return a;}int main(){ // Is a temporary allocated on the heap to support this,even
请考虑以下代码,它以“嵌套”方式将临时对象绑定到const引用:

#include <iostream>

std::string foo()
{
    return "abc";
}

std::string goo()
{
    const std::string & a = foo();
    return a;
}

int main()
{
    // Is a temporary allocated on the heap to support this,even for a moment?
    const std::string & b = goo();
}

我一直试图理解编译器在内存存储方面必须做些什么才能支持这种“嵌套”结构.

我怀疑对于foo()的调用,内存分配很简单:当函数foo()退出时,std :: string的存储将在堆栈上分配.

但是,编译器必须做些什么来支持b引用的对象的存储?函数goo的堆栈必须展开并“替换为”b引用的堆栈上的对象,但是为了展开goo的堆栈,编译器是否需要在堆上暂时创建对象的副本(在将其复制回不同位置的堆栈之前)?

或者编译器是否可以完成此构造的要求而不在堆上分配任何存储,即使是暂时的?

或者甚至可以让编译器对b所引用的对象使用与a引用的对象相同的存储位置,而不在堆栈或堆上进行任何额外的分配?

解决方法

下面是C标准允许编译器重建代码的示例.我正在使用完整的NRVO.注意使用placement new,这是一个中等模糊的C功能.你传递一个新的指针,它在那里而不是在免费商店中构造结果.

#include <iostream>

void __foo(void* __construct_std_string_at)
{
  new(__construct_std_string_at)std::string("abc");
}

void __goo(void* __construct_std_string_at)
{
  __foo(__construct_std_string_at);
}

int main()
{
  unsigned char __buff[sizeof(std::string)];
  // Is a temporary allocated on the heap to support this,even for a moment?
  __goo(&__buff[0]);
  const std::string & b = *reinterpret_cast<std::string*>(&__buff[0]);
  // ... more code here using b I assume
  // end of scope destructor:
  reinterpret_cast<std::string*>(&__buff[0])->~std::string();
}

如果我们在goo中阻止了NRVO,那么它看起来就像

#include <iostream>

void __foo(void* __construct_std_string_at)
{
  new(__construct_std_string_at)std::string("abc");
}

void __goo(void* __construct_std_string_at)
{
  unsigned char __buff[sizeof(std::string)];
  __foo(&__buff[0]);
  std::string & a = *reinterpret_cast<std::string*>(&__buff[0]);
  new(__construct_std_string_at)std::string(a);
  // end of scope destructor:
  reinterpret_cast<std::string*>(&__buff[0])->~std::string();
}

int main()
{
  unsigned char __buff[sizeof(std::string)];
  // Is a temporary allocated on the heap to support this,even for a moment?
  __goo(&__buff[0]);
  const std::string & b = *reinterpret_cast<std::string*>(&__buff[0]);
  // ... more code here using b I assume
  // end of scope destructor:
  reinterpret_cast<std::string*>(&__buff[0])->~std::string();
}

基本上,编译器知道引用的生命周期.因此,它可以创建存储变量的实际实例的“匿名变量”,然后创建对它的引用.

我还注意到,当你调用一个函数时,你有效地(隐式地)将一个指向缓冲区的指针传递给返回值所在的位置.因此被调用函数在调用者的作用域中构造对象“就地”.

使用NRVO,被调用函数作用域中的命名变量实际上是在调用函数“返回值所在的位置”中构造的,这使得返回变得容易.没有它,你必须在本地做所有事情,然后在return语句中通过相应的placement new将你的返回值复制到指向你的返回值的隐式指针.

没有什么需要在堆(也就是免费商店)上完成,因为生命周期都很容易证明和堆栈排序.

具有预期签名的原始foo和goo必须仍然存在,因为它们具有外部链接,直到在发现没有人使用它们时可能被丢弃.

所有以__开头的变量和函数仅用于展示.编译器/执行环境不再需要具有命名变量,而不需要具有红血球名称. (理论上,因为__是保留的,在编译之前执行这样的转换传递的编译器可能是合法的,如果你实际使用了那些变量名并且编译失败,那将是你的错,而不是编译器的错,但是……那将是一个非常hackey编译器.;))

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读