考虑这个程序:
#include <stdio.h>
struct S {
S() { print(); }
void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
据我所知,这里只构建了一个S
对象。没有副本删除:首先没有副本被删除,事实上,如果我明确删除了副本和/或移动构造函数,编译器继续接受该程序。
但是,我看到打印了两个不同的指针值。发生这种情况是因为我的平台的ABI在CPU寄存器中返回了一些可复制的类型,例如这个类型,所以ABI无法避免复制。 clang即使在完全优化掉函数调用时也会保留这种行为。如果我给S
一个非平凡的复制构造函数,即使它不可访问,那么我确实看到相同的值打印了两次。
对print()
的初始调用发生在构造期间,即在对象生命周期开始之前,但在构造函数中使用this
通常是有效的,只要它不以某种方式使用这需要构造完成 - 例如,没有铸造到派生类 - 据我所知,打印或存储它的值不需要构造完成。
标准是否允许此程序打印两个不同的指针值?
注意:我知道标准允许这个程序打印相同指针值的两个不同表示,从技术上讲,我没有排除它。我可以创建一个避免比较指针表示的不同程序,但它会更难理解,所以我想尽可能避免这种情况。
答案 0 :(得分:5)
T.C。评论中指出,这是标准中的缺陷。这是core language issue 1590。这是一个与我的例子略有不同的问题,但原因相同:
有些ABI要求在寄存器[...]中传递某些类类型的对象。应更改标准以允许此用法。
current suggested wording将通过向标准添加新规则来涵盖这一点:
当类型
X
的对象传递给函数或从函数返回时,如果X
的每个复制构造函数,移动构造函数和析构函数都是微不足道的或删除的,{{1}至少有一个未删除的副本或移动构造函数,允许实现创建一个临时对象来保存函数参数或结果对象。 [...]
在大多数情况下,这将允许当前的GCC / clang行为。
有一个小角落的情况:当前,当一个类型只有一个删除的副本或移动构造函数,如果默认是默认的,根据标准的当前规则,如果删除该构造函数仍然是微不足道的:
12.8复制和移动类对象[class.copy]
12如果不是用户提供的,
X
类的复制/移动构造函数是微不足道的[...]
删除的拷贝构造函数不是用户提供的,后面的任何内容都不会使这样的拷贝构造函数变得非常重要。因此,按照标准的规定,这样的构造函数是微不足道的,而as specified by my platform's ABI,由于琐碎的构造函数,GCC和clang也会在这种情况下创建一个额外的副本。我的测试程序的一行添加证明了这一点:
X
即使建议的分辨率要求打印两次相同的地址,也会打印出GCC和clang两个不同的地址。这似乎表明,虽然我们将更新标准以不需要完全不兼容的ABI,但我们仍然需要更新ABI以便以与标准所需的方式兼容的方式处理极端情况。 / p>
答案 1 :(得分:1)
在这种情况下,这不是一个答案,而是一个注释关于 g ++ 和 clang 的不同行为,具体取决于{{ 1}}优化标志。
请考虑以下代码:
-O
我们可以看到clang(3.8)和g ++(6.1)有点不同,但两者都得到了正确的答案。
#include <stdio.h>
struct S {
int i;
S(int _i): i(_i) {
int* p = print("from ctor");
printf("about to put 5 in %p\n", (void *)&i);
*p = 5;
}
int* print(const char* s) {
printf("%s: %p %d %p\n", s, (void *) this, i, (void *)&i);
return &i;
}
};
S f() { return {3}; }
int main() {
f().print("from main");
}
from ctor: 0x7fff9d5e86b8 3 0x7fff9d5e86b8
about to put 5 in 0x7fff9d5e86b8
from main: 0x7fff9d5e86b0 5 0x7fff9d5e86b0
似乎他们在两种情况下都做对了 - 当他们决定跳过寄存器优化(g ++ -O2)时,以及当他们使用寄存器优化但是将值复制到实际i时(所有其他情况) )。