在 C++ 复制省略期间如何管理内存?
How is memory managed during C++ copy elision?
#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
据我所知,只创建了 A 的一个实例,而不是 A a 和 A b 的 2 个实例,因为复制省略或 return 值优化。
但是从内存的角度来看上面的程序,object a 位于函数 beta 的堆栈激活记录或堆栈 space 中,并且具有内存位置 0x7ffc12bdaf77 。当它 returns 来自 beta 时,复制省略使对象 b 与对象 a 相同而不是复制它。所以,现在 b 也有地址 0x7ffc12bdaf77 。我无法理解 if b 仍然是函数 main 的局部并且存在于它的堆栈中 space,它怎么会占用 main 堆栈之外的内存 space ?
在 Microsoft x64 调用约定中,一个隐藏指针将作为 beta
的第一个参数添加到前面。该指针包含 b
的地址,它将位于 main
的堆栈帧上。该指针将用于立即在 main
的堆栈帧上同时创建 a
和 b
。所以换句话说,a
不存在于 beta
的栈帧上;从内存的角度来看,a
和 b
是等价的。
As far as I understand just a single instance of A was created rather than 2
正确。
how can it occupy a memory outside main's stack space ?
没有。
return 值只是在 main
调用的堆栈帧中创建的。
考虑这一行:A b = beta();
。编译器如何实现它?
嗯,beta
是一些其他功能。 beta
的 return 值是 A
。所以有两种可能的方法来实现这一点。编译器可以让 beta
为其 A
return 值分配堆栈 space,但这可能会有问题,因为调用者需要使用该 return 值。因此,编译器让 caller 为 return 值分配堆栈 space。毕竟,调用者确实知道 return 值的 size/alignment,因此它拥有分配 space.
所需的一切信息
所以让我们选择后者。这意味着当编译器调用 beta
时,它会传入 beta
的 return 值所在的地址。但这也意味着编译器,对于 beta
的这个特定调用,可以只给 beta
的 return 值与它给 b
.[=30 的地址相同的地址=]
所以就在那里,我们将函数的 return 值的副本删除到 b
。
所以当编译器去编译 beta
时,它知道调用者将给它一个指向 return 值应该去哪里的指针。所以 return a;
语句从 a
变量语义上复制到这个 return 值对象中。
但是,编译器可以看到整个 beta
。并且可以看出a
变量是一个局部变量,在所有的控制路径上都得到了returned。因此,编译器可以将 a
放入调用者为 return 值提供的内存中,而不是给 a
一个单独的堆栈地址。
所以,我们再次将 a
中的一个副本删除到 return 值中。
#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
据我所知,只创建了 A 的一个实例,而不是 A a 和 A b 的 2 个实例,因为复制省略或 return 值优化。 但是从内存的角度来看上面的程序,object a 位于函数 beta 的堆栈激活记录或堆栈 space 中,并且具有内存位置 0x7ffc12bdaf77 。当它 returns 来自 beta 时,复制省略使对象 b 与对象 a 相同而不是复制它。所以,现在 b 也有地址 0x7ffc12bdaf77 。我无法理解 if b 仍然是函数 main 的局部并且存在于它的堆栈中 space,它怎么会占用 main 堆栈之外的内存 space ?
在 Microsoft x64 调用约定中,一个隐藏指针将作为 beta
的第一个参数添加到前面。该指针包含 b
的地址,它将位于 main
的堆栈帧上。该指针将用于立即在 main
的堆栈帧上同时创建 a
和 b
。所以换句话说,a
不存在于 beta
的栈帧上;从内存的角度来看,a
和 b
是等价的。
As far as I understand just a single instance of A was created rather than 2
正确。
how can it occupy a memory outside main's stack space ?
没有。
return 值只是在 main
调用的堆栈帧中创建的。
考虑这一行:A b = beta();
。编译器如何实现它?
嗯,beta
是一些其他功能。 beta
的 return 值是 A
。所以有两种可能的方法来实现这一点。编译器可以让 beta
为其 A
return 值分配堆栈 space,但这可能会有问题,因为调用者需要使用该 return 值。因此,编译器让 caller 为 return 值分配堆栈 space。毕竟,调用者确实知道 return 值的 size/alignment,因此它拥有分配 space.
所以让我们选择后者。这意味着当编译器调用 beta
时,它会传入 beta
的 return 值所在的地址。但这也意味着编译器,对于 beta
的这个特定调用,可以只给 beta
的 return 值与它给 b
.[=30 的地址相同的地址=]
所以就在那里,我们将函数的 return 值的副本删除到 b
。
所以当编译器去编译 beta
时,它知道调用者将给它一个指向 return 值应该去哪里的指针。所以 return a;
语句从 a
变量语义上复制到这个 return 值对象中。
但是,编译器可以看到整个 beta
。并且可以看出a
变量是一个局部变量,在所有的控制路径上都得到了returned。因此,编译器可以将 a
放入调用者为 return 值提供的内存中,而不是给 a
一个单独的堆栈地址。
所以,我们再次将 a
中的一个副本删除到 return 值中。