C++ 在C++;复制省略?
据我所知,由于拷贝省略或返回值优化,只创建了一个a实例,而不是a a和a b的两个实例。C++ 在C++;复制省略?,c++,c++11,copy-elision,C++,C++11,Copy Elision,据我所知,由于拷贝省略或返回值优化,只创建了一个a实例,而不是a a和a b的两个实例。 但从内存的角度来看上述程序,对象a位于函数beta的堆栈激活记录或堆栈空间内,内存位置为0x7ffc12bdaf77。当它从测试版返回时,复制省略使对象b与对象a完全相同,而不是复制它。因此,现在b也有地址0x7ffc12bdaf77。我无法理解,如果b仍然是函数main的一个本地函数,并且存在于其堆栈空间内,它如何能够占用main堆栈空间外的内存?在microsoft x64调用约定中,隐藏指针将作为be
但从内存的角度来看上述程序,对象a位于函数beta的堆栈激活记录或堆栈空间内,内存位置为0x7ffc12bdaf77。当它从测试版返回时,复制省略使对象b与对象a完全相同,而不是复制它。因此,现在b也有地址0x7ffc12bdaf77。我无法理解,如果b仍然是函数main的一个本地函数,并且存在于其堆栈空间内,它如何能够占用main堆栈空间外的内存?在microsoft x64调用约定中,隐藏指针将作为
beta
的第一个参数。此指针包含b
的地址,该地址将位于main
的堆栈框架上。此指针将用于同时在main
的堆栈帧上立即创建a
和b
。所以换句话说,a
在beta
的堆栈框架中的任何地方都不存在;从内存角度来看,a
和b
将是等效的
据我所知,只创建了a的一个实例,而不是2个
对
它如何占据main堆栈空间之外的内存
没有
返回值只是在
main
调用的堆栈框架中创建的。考虑这一行:ab=beta()代码>。编译器是如何实现的
嗯,beta
是另一种功能。而beta
的返回值为a
。所以有两种可能的方法来实现这一点。编译器可以使beta
为其A
返回值分配堆栈空间,但这可能会有问题,因为调用者需要使用该返回值。因此,编译器让调用方为返回值分配堆栈空间。毕竟,调用方确实知道返回值的大小/对齐方式,因此它拥有分配该空间所需的一切
那么,让我们选择后者。这意味着当编译器调用beta
时,它会传入beta
返回值所在的地址。但是这也意味着编译器,对于这个特定的beta
调用,可以给beta
的返回值提供与它将给b
相同的地址
在这里,我们将函数返回值中的副本省略为b
因此,当编译器开始编译beta
时,它知道调用者将给它一个指针,指向返回值应该去的地方。因此返回a
语句从变量语义上复制到此返回值对象中
但是,编译器可以看到整个beta
。它可以看到a
变量是一个局部变量,它在所有控制路径上返回。因此,编译器可以将a
放在调用者为返回值提供的内存中,而不是给a
一个单独的堆栈地址
因此,我们再次将a
中的一个副本省略到返回值中。这让我感到惊讶。只能看到函数声明的编译器如何知道这个隐藏指针?还是总是有一个指针传递给可选的复制省略返回值的填充?@swithwings:因为编译器就是这样编译返回值(或者更确切地说是一个太大或太复杂而无法放入寄存器的值)的函数的。这是调用约定的一部分,完全由函数的声明决定。@Nicolas好的,那么这是ABI在所有情况下如何实现返回值的内置特性?有道理。我很困惑,因为在C++17之前,NRVO是一个“优化”,因此您通常需要函数定义来知道它是否可以完成(即使这样,它“可能”还没有完成)。但是,经过进一步思考,我想总的来说,没有任何其他方法可以实现这一点?正是因为这个原因DIT出现在我之后,C++ 17“保证删除”规则要求这样做,因此每个C++ ABI必须这样工作,而不仅仅是MS的X64调用约定。(因为我不知道在任何主流播放窗体上C++ 17的任何ABI中断)@ AtasoRessWuffEng:记住,根据C++,有两个副本:从返回到返回值对象的拷贝,以及从返回值对象到接收端堆栈变量的拷贝。函数的实现可以省略其中一个,而接收方可以省略另一个。如果两者都被省略,那么就存在完全省略。
#include <iostream>
using namespace std;
class A
{
public :
A()
{
cout<<"constructor is called"<<endl;
}
~A()
{
cout<<"destructor is called"<<endl;
}
A(const A &s)
{
cout<<"copy constructor is called"<<endl;
}
};
A beta()
{
A a;
cout<<"mem location a : "<<&a<<endl;
return a;
}
int main(int argc, char** argv) {
A b = beta();
cout<<"mem location b : "<<&b<<endl;
return 0;
}
constructor is called
mem location a : 0x7ffc12bdaf77
mem location b : 0x7ffc12bdaf77
destructor is called