C++ 在优化后按值返回类对象的内存位置

C++ return by value class objects's memory whereabouts in wake of optimizations

提问人:codepoet 提问时间:7/22/2023 更新时间:7/22/2023 访问量:76

问:

假设有一个用户定义。一些帖子建议 C++ 类对象“永远不会”在堆上分配,除非分配了 .但!另一方面,有些帖子建议,按值从函数返回本地外观的类对象不一定复制任何数据。所以!这种对象的数据首先存储在哪里?它还是堆叠的吗?它是否被提升为调用函数的堆栈帧?class Foonew

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++ 堆栈帧 复制省略 返回值优化

评论

1赞 463035818_is_not_an_ai 7/22/2023
“按函数中的值在本地查找类对象不一定复制任何数据”这是不正确的。这个问题是基于错误的前提。要详细回答它,您需要展示什么是...
2赞 Some programmer dude 7/22/2023
当你说没有复制时,也许你正在考虑复制省略
1赞 Ted Lyngmo 7/22/2023
“编译器是否自动重构代码以插入删除” - 不,这不会发生。
0赞 codepoet 7/22/2023
@463035818_is_not_an_ai 检查帖子底部的链接。它指向一个公认的问题答案,导致我问这个问题。这里的函数 a 几乎是从那里盖章的。如果这是一个错误的前提,那么该帖子也是错误的。如果您的观点是“取决于”何时执行此类优化,欢迎您在答案中发布这两种情况。
2赞 Konrad Rudolph 7/22/2023
C++ 中的堆分配是手动进行的,而不是自动进行的。返回值并不总是被复制。这两种说法(不是“建议”)之间没有矛盾。

答:

5赞 n. m. could be an AI 7/22/2023 #1

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

避免复制的另一种方法是尽可能使用移动而不是复制。移动语义是一个单独的故事,超出了这个答案的范围。

2赞 anatolyg 7/22/2023 #2

对于这样的问题,你不可能有一个笼统的答案。如果您的代码足够简单,则存储不在堆栈中,而是在寄存器中。

如果函数按值返回,则存储要么在堆栈上,要么在寄存器中,这取决于对象的大小(也取决于编译器的详细信息)。

谈论“对象在哪里”可能毫无意义——编译器可能会将其优化为不存在。如果反汇编已编译的代码,可能会发现对象的存储位置,但这可能需要一些想象力:例如,对象的一半数据可能在堆栈上,另一半在寄存器中。或者一半在寄存器中,一半在优化中。

0赞 HolyBlackCat 7/22/2023 #3

简短的回答:

假设未进行复制(编译器执行 NRVO),调用方将为堆栈上的对象分配内存,并将指向该对象的指针传递给被调用方,该指针将调用该位置上类的构造函数。

当然,这假设没有优化(NRVO 本身除外)。

例如,一个足够小和简单(没有重要的复制/移动操作?)的类可能会在寄存器中返回。