什么可以防止类中相邻成员重叠

What prevents overlapping of adjacent members in classes?

本文关键字:成员 重叠 什么      更新时间:2023-10-16

考虑以下三个struct

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  

blubblob成员的存储之间没有重叠,即使大小 1blob原则上可以"适合"blub的填充。

C++20 引入了no_unique_address属性,该属性允许相邻的空成员共享同一地址。它还显式允许上述使用一个成员的填充来存储另一个成员的方案。来自 cpp偏好(强调我的):

指示此数据成员不需要具有不同于其类的所有其他非静态数据成员的地址。这意味着,如果成员具有空类型(例如无状态分配器),编译器可以将其优化为不占用空间,就像它是空基一样。如果成员不为空,则其中的任何尾部填充也可以重用于存储其他数据成员。

事实上,如果我们在blub b0上使用这个属性,bla的大小下降到8,所以blob确实存储在blub中,如在 godbolt 上看到的那样。

最后,我们进入我的问题:

标准(C++11 到 C++20)中的哪些文本可以防止这种重叠而不no_unique_address,对于不可轻易复制的对象?

我需要从上面排除简单可复制 (TC) 对象,因为对于 TC 对象,允许从一个对象std::memcpy到另一个对象,包括成员子对象,如果存储重叠,这将中断(因为相邻成员的全部或部分存储将被覆盖)2


1我们以递归方式将填充简单地计算为结构大小与其所有组成成员的大小之间的差异。

2这就是为什么我定义了复制构造函数:使blubblob不是平凡的可复制的。

在谈论内存模型时,该标准非常安静,并且对它使用的某些术语不是很明确。但我想我找到了一个有效的论点(可能有点弱)

首先,让我们找出什么是对象的一部分。[基本类型]/4:

类型T的对象表示是类型为T的对象所占用的Nunsigned char对象的序列,其中N等于sizeof(T)。类型T的对象的值表示形式是参与表示类型T的值的位集。对象表示形式中不属于值表示形式的位是填充位。

因此,b0的对象表示由sizeof(blub)unsigned char个对象组成,因此为 8 个字节。填充位是对象的一部分。

如果另一个对象不是嵌套在其中的,则任何对象都不能占用另一个对象的空间[basic.life]/1.5:

T类型的对象的生存期o在以下情况下结束:

[...]

(1.5) 对象占用的存储被释放,或被未嵌套在o中的对象([intro.object])重用。

因此,当b0占用的存储将被另一个对象重用时,的生命周期将结束,即b1.我没有检查过,但我认为标准要求一个活着的对象的子对象也应该是活的(我无法想象这应该如何以不同的方式工作)。

因此,b0占用的存储空间可能无法被b1使用。我在标准中没有找到"占用"的定义,但我认为合理的解释是"对象表示的一部分"。在描述对象表示的引号中,使用了"占用"一词1.在这里,这将是 8 个字节,因此bla至少还需要一个b1

特别是对于子对象(因此,除其他非静态数据成员外),还有规定 [intro.object]/9(但这是在 C++20 中添加的,谢谢@BeeOnRope)

如果两个对象嵌套在另一个对象中,或者至少有一个对象是零大小的子对象并且它们属于不同的类型,则两个具有重叠生存期但不是位字段的对象可能具有相同的地址;否则,它们具有不同的地址并占用不相交的存储字节

(强调我的)在这里,我们再次遇到"占用"未定义的问题,我再次主张在对象表示中获取字节。请注意,此 [basic.memobj]/脚注 29 有一个脚注

在"as-if"规则下,如果程序无法观察到差异,则允许实现将两个对象存储在同一个机器地址上,或者根本不存储对象([intro.execution])。

如果编译器可以证明没有可观察到的副作用,则可能允许编译器打破这一点。我认为对于像对象布局这样基本的东西来说,这是非常复杂的。也许这就是为什么仅当用户通过添加[no_unique_address]属性提供没有理由具有不相交对象的信息时,才会进行此优化。

tl;dr:填充可能是对象的一部分,成员必须是不相交的。


1我忍不住添加了一个引用,占领可能意味着要占用:韦伯斯特修订的未删节词典,G. & C. Merriam,1913年(强调我的)

  1. 保持或填充尺寸;占用房间或空间;覆盖或填充;因为,营地占地五英亩。J.赫歇尔爵士。

没有字典爬网,什么标准爬网是完整的?