c – 是否会在堆上分配内存以支持临时对象与const引用的嵌套绑定
请考虑以下代码,它以“嵌套”方式将临时对象绑定到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编译器.;)) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |