提问人:codepoet 提问时间:7/22/2023 更新时间:7/22/2023 访问量:76
C++ 在优化后按值返回类对象的内存位置
C++ return by value class objects's memory whereabouts in wake of optimizations
问:
假设有一个用户定义。一些帖子建议 C++ 类对象“永远不会”在堆上分配,除非分配了 .但!另一方面,有些帖子建议,按值从函数返回本地外观的类对象不一定复制任何数据。所以!这种对象的数据首先存储在哪里?它还是堆叠的吗?它是否被提升为调用函数的堆栈帧?class Foo
new
class Foo {
...
}
Foo a(int x) {
Foo result;
doabc(x, result);
return result;
}
Foo b(int x) {
Foo result = a(x);
doxyz(x,result);
return result;
}
int main() {
int x;
cin >> x;
Foo result = b(x);
dosomethingelse(result);
cout << result;
}
如果 from a 的 Foo 结果不是按值复制的,那么它分配在哪里?堆还是堆?如果在堆上,编译器是否会自动重构代码以插入删除?如果在堆栈上,它将位于哪个堆栈帧上?B的?这是让我想知道的帖子:https://stackoverflow.com/a/17473874/15239054。谢谢!
答:
C++ 语言中没有堆或堆栈。这些是实现的属性,而不是语言的属性。只要生成的程序按照标准要求执行操作,实现就可以自由地执行任何它想做的事情。这包括为堆上的自动变量或临时变量分配存储。
不过,“正常”实现不会这样做。自动变量和临时变量都在堆栈或寄存器中分配。
在您的示例中,所有命名的变量都具有自动存储持续时间,这通常意味着它们在堆栈上分配(因为实现具有“堆栈”之类的东西——语言对此保持沉默)。从一个变量复制到另一个变量通常不涉及任何需要在某处分配的第三个变量或临时变量。这包括从函数返回值的情况。result
对象可能拥有资源,例如内存,这些资源需要在自由存储中分配(“堆”是指具有堆的实现)。如果是这种情况,则复制构造函数或复制赋值运算符负责复制,并在需要的地方分配内容。但这是关于对象拥有的资源,而不是对象本身。
您链接的问题是关于避免复制,特别是避免复制拥有的资源。这与上述任何一项都无关。
允许(有时需要)实现来省略副本。复制省略是避免复制的一种方法。下面是一个示例:
#include <iostream>
struct Foo
{
Foo() {}
Foo(const Foo&) { std::cout << "Copied by ctor\n"; };
Foo& operator=(const Foo&) { std::cout << "Copied by assignment\n"; return *this; };
};
Foo func()
{
Foo foo;
return foo;
}
int main()
{
Foo foo (func());
}
现场演示。此示例显示了强制和可选的复制省略。
在 C++14 及更低版本中,有两种可选的复制省略情况。在 C++17 及更高版本中,有一种情况是可选的,一种情况是强制的复制省略(C++17 中的一个新特性,官方称为“临时具体化”)。默认情况下,GCC 执行可选的复制省略,但可以使用 禁用。这是标准允许的优化。不能禁用强制复制省略,因为标准强制要求这样做。-fno-elide-constructors
避免复制的另一种方法是尽可能使用移动而不是复制。移动语义是一个单独的故事,超出了这个答案的范围。
对于这样的问题,你不可能有一个笼统的答案。如果您的代码足够简单,则存储不在堆栈中,而是在寄存器中。
如果函数按值返回,则存储要么在堆栈上,要么在寄存器中,这取决于对象的大小(也取决于编译器的详细信息)。
谈论“对象在哪里”可能毫无意义——编译器可能会将其优化为不存在。如果反汇编已编译的代码,可能会发现对象的存储位置,但这可能需要一些想象力:例如,对象的一半数据可能在堆栈上,另一半在寄存器中。或者一半在寄存器中,一半在优化中。
简短的回答:
假设未进行复制(编译器执行 NRVO),调用方将为堆栈上的对象分配内存,并将指向该对象的指针传递给被调用方,该指针将调用该位置上类的构造函数。
当然,这假设没有优化(NRVO 本身除外)。
例如,一个足够小和简单(没有重要的复制/移动操作?)的类可能会在寄存器中返回。
评论
...