C++标准是否保证函数返回值具有常量地址?

16 c++ return-value abi language-lawyer c++11

考虑这个程序:

#include <stdio.h>
struct S {
  S() { print(); }
  void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
Run Code Online (Sandbox Code Playgroud)

据我所知,这里只S构建了一个对象.没有副本省略:首先没有副本被删除,事实上,如果我明确删除了副本和/或移动构造函数,编译器继续接受该程序.

但是,我看到打印了两个不同的指针值.发生这种情况是因为我的平台的ABI在CPU寄存器中返回了一些可复制的类型,例如这个类型,因此ABI无法避免复制.clang即使在完全优化掉函数调用时也会保留这种行为.如果我给出S一个非平凡的复制构造函数,即使它不可访问,那么我确实看到相同的值打印两次.

print()在构造期间发生的初始调用,即在对象生命周期开始之前,但this在构造函数内部使用通常是有效的,只要它不以需要构造完成的方式使用 - 没有强制转换为例如派生类 - 据我所知,打印或存储其值不需要构造完成.

标准是否允许此程序打印两个不同的指针值?

注意:我知道该标准允许该程序打印相同指针值的两个不同表示,从技术上讲,我没有排除这一点.我可以创建一个避免比较指针表示的不同程序,但它会更难理解,所以我想尽可能避免这种情况.

小智 5

TC在评论中指出这是标准中的缺陷.这是1590年的核心语言问题.这是一个与我的例子略有不同的问题,但根本原因相同:

一些ABI要求在寄存器中传递某些类类型的对象[...].应更改标准以允许此用法.

当前建议的措词将通过添加新的规则,标准涵盖这样的:

当类类型的对象X传递给函数或从函数返回时,如果每个复制构造函数,移动构造函数和析构函数X是微不足道的或删除的,并且X至少有一个未删除的副本或移动构造函数,则允许实现创建用于保存函数参数或结果对象的临时对象.[...]

在大多数情况下,这将允许当前的GCC/clang行为.

有一个小角落的情况:当前,当一个类型只有一个删除的副本或移动构造函数,如果默认是默认的,根据标准的当前规则,如果删除该构造函数仍然是微不足道的:

12.8复制和移动类对象[class.copy]

12 X如果用户不提供类的复制/移动构造函数是微不足道的[...]

删除的复制构造函数不是用户提供的,并且后面的任何内容都不会使这样的复制构造函数变得非常重要.因此,正如标准所规定的那样,这样的构造函数是微不足道的,并且由我的平台的ABI指定,由于琐碎的构造函数,GCC和clang也在这种情况下创建了一个额外的副本.我的测试程序的一行添加证明了这一点:

#include <stdio.h>
struct S {
  S() { print(); }
  S(const S &) = delete;
  void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
Run Code Online (Sandbox Code Playgroud)

这会打印两个带有GCC和clang的不同地址,即使提议的分辨率也需要打印两次相同的地址.这似乎表明,虽然我们将获得标准的更新以不需要完全不兼容的ABI,但我们仍然需要更新ABI以便以与标准所需的方式兼容的方式处理极端情况.