【发布时间】:2020-05-09 01:58:24
【问题描述】:
考虑以下三个structs:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
在int为4字节的典型平台上,大小、对齐方式和总填充1如下:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
blub 和 blob 成员的存储之间没有重叠,即使大小 1 blob 原则上可以“适合”blub 的填充。
C++20 引入了no_unique_address 属性,允许相邻的空成员共享同一个地址。它还明确允许上述使用一个成员的填充来存储另一个成员的场景。来自cppreference(强调我的):
表示该数据成员不需要具有与其类的所有其他非静态数据成员不同的地址。这意味着如果成员有一个空类型(例如无状态分配器),编译器可以优化它以不占用空间,就像它是一个空基一样。 如果成员不为空,则其中的任何尾部填充也可以重复用于存储其他数据成员。
确实,如果我们在blub b0上使用这个属性,bla的大小会下降到8,所以blob确实存储在blubas seen on godbolt中。
最后,我们来回答我的问题:
标准中的哪些文本(C++11 到 C++20)可以防止在没有 no_unique_address 的情况下出现这种重叠,对于不可复制的对象?
我需要从上面排除普通可复制 (TC) 对象,因为对于 TC 对象,允许 std::memcpy 从一个对象到另一个对象,包括成员子对象,如果存储重叠,这会中断(因为将覆盖相邻成员的全部或部分存储空间)2.
1我们将填充简单地计算为结构大小与其所有组成成员的大小之间的差值,递归。
2这就是我定义复制构造函数的原因:使blub 和blob 不能简单地复制。
【问题讨论】:
-
我没有研究过,但我猜测“好像”规则。如果与抽象机器(这是您的代码编译所针对的)没有可观察到的差异(一个具有非常特定含义的术语),那么编译器可以随意更改代码。
-
很确定这是一个骗局:stackoverflow.com/questions/53837373/…
-
@JesperJuhl - 对,但我问的是为什么不能,而不是为什么可以,以及“好像”规则通常适用于前者,但对后者没有意义。此外,“好像”对于通常是全局问题而非局部问题的结构布局并不清楚。最终,编译器必须有一套一致的布局规则,除了可能证明永远不会“转义”的结构。
-
@BeeOnRope 我无法回答您的问题,抱歉。这就是为什么我只发表评论而不是答案的原因。您在该评论中得到的答案是我对解释的最佳猜测,但我不知道答案(我很想自己学习 - 这就是您获得支持的原因)。
-
@NicolBolas - 您是否回答了正确的问题?这与检测安全副本或其他任何内容无关。相反,我很好奇为什么不能在成员之间重复使用填充。无论如何,你都错了:trivially copyable 是property of the type,而且一直都是。然而,为了安全地复制一个对象,它必须 both 具有 TC 类型(类型的属性),而不是潜在重叠的主体(对象的属性,我猜这是你弄糊涂了)。仍然不知道为什么我们在这里谈论副本。
标签: c++ language-lawyer c++20