是什么防止班级中相邻成员的重叠?


12

考虑以下三个struct

class blub {
    int i;
    char c;

    blub(const blub&) {}
};

class blob {
    char s;

    blob(const blob&) {}
};

struct bla {
    blub b0;
    blob b1;
};

int4字节的典型平台上,大小,对齐方式和总填充1如下:

  struct   size   alignment   padding  
 -------- ------ ----------- --------- 
  blub        8           4         3  
  blob        1           1         0  
  bla        12           4         6  

即使大小为1 的原则上可以“适合”的填充,在blubblob成员的存储之间也没有重叠。blobblub

C ++ 20引入了该no_unique_address属性,该属性允许相邻的空成员共享同一地址。它还明确允许上述使用一个成员的填充存储另一个成员的方案。来自cppreference(重点是我的):

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

确实,如果我们在上使用此属性blub b0,则bladrop 的大小为to 8,因此blob确实存储在blub godbolt上看到的中

最后,我们要问我的问题:

no_unique_address对于无法平凡复制的对象,标准(C ++ 11到C ++ 20)中的哪些文本不使用来防止这种重叠?

我需要从上面排除琐碎的可复制(TC)对象,因为对于TC对象,允许将其std::memcpy从一个对象转移到另一个对象,包括成员子对象,并且如果存储重叠,则这会损坏(因为全部或部分存储)因为相邻成员将被覆盖)2


1我们将递归简单地计算为结构大小与其所有组成成员的大小之差。

2这就是为什么我定义了复制构造函数的原因:make blubblob不是琐碎的copyable


我还没有研究过,但是我是“好像”规则。如果与抽象机(编译您的代码所依据的)没有明显的区别(术语具有非常具体的含义),则编译器可以随意更改代码。
Jesper Juhl

很肯定这是这种愚弄的人:stackoverflow.com/questions/53837373/...
NathanOliver

@JesperJuhl-是的,但是我想问为什么不能这样做为什么不可以呢? “好像”规则通常适用于前者,但对后者却没有意义。此外,对于结构布局来说“好像”还不清楚,通常这是全球性的关注,而不是局部性的关注。最终,编译器必须具有一套一致的布局规则,除了可能不会证明其“逃逸”的结构。
BeeOnRope

1
@BeeOnRope对不起,我无法回答您的问题。这就是为什么我只是发表评论而不是回答。您在该评论中得到的是我对解释的最佳猜测,但我不知道答案(急于亲自学习它-这就是为什么您表示反对)。
Jesper Juhl

1
@NicolBolas-您在回答正确的问题吗?这与检测安全副本或其他任何内容无关。相反,我很好奇为什么成员之间不能重复使用填充。无论如何,您错了:琐碎可复制该类型属性,并且一直存在。但是,为了安全地复制对象,它必须具有TC类型(该类型的属性),又不能具有潜在的重叠主题(对象的属性,我想这就是您感到困惑的地方)。仍然不知道为什么我们在这里谈论拷贝。
BeeOnRope

Answers:


1

在谈论内存模型时,该标准非常安静,而在使用它的某些术语时却不太明确。但是我想我发现了一个可行的论点(可能有点弱)

首先,让我们找出什至是对象的一部分。[basic.types] / 4

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

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

如果对象不是嵌套在其中的对象,那么它就不能占据另一个对象的空间[basic.life] /1.5

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

[...]

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

因此b0,当它所占用的存储将被另一个对象(即)重用时,其寿命将结束b1。我还没有检查过,但是我认为标准要求活动对象的子对象也应该活动(而且我无法想象这应该如何工作)。

因此,b0 占用的存储空间可能不会被占用b1。我没有在标准中定义“占用”,但我认为合理的解释将是“对象表示的一部分”。在报价descriping对象表示,词语“占用”用于1。在这里,这将是8个字节,因此bla至少需要一个b1

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

如果一个对象嵌套在另一个对象中,或者如果至少一个对象是零大小的子对象并且它们是不同类型,则两个寿命不同且不是位域的对象可能具有相同的地址;否则,它们具有不同的地址并占用不相交的storage字节

(重点是我的)在这里,我们再次遇到一个问题,即“占用”未定义,我再次争辩要采用对象表示形式中的字节。请注意,此[basic.memobj] / footnote 29有一个脚注

根据“假设”规则,如果程序无法观察到差异,则允许实现将两个对象存储在同一机器地址或根本不存储对象([intro.execution])。

如果编译器可以证明没有可观察到的副作用,则可能会破坏这一点。我认为对于诸如对象布局之类的基本事物而言,这相当复杂。也许这就是为什么仅在用户通过添加[no_unique_address]属性来提供没有理由使对象不相交的信息时才进行此优化的原因。

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


1我忍不住要添加一个可能会占用的参考文献:韦伯斯特修订的未删节词典,G。&C. Merriam,1913年(强调我的观点)

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

没有字典爬网,将完成什么标准的爬网?


2
我认为,to.storage的“占用存储空间不相交字节”部分对我来说已经足够了-但此措辞仅在C ++ 20中作为添加的更改的一部分添加no_unique_address。这使得C ++ 20之前的情况不太清楚。我不理解您的推理导致从basic.life/1.5中得出“如果没有对象嵌套,则任何对象都不能占据另一个空间”,尤其是如何从“释放对象所占的存储空间”中获取信息“没有对象可以占据另一个空间”。
BeeOnRope

1
我对该段做了一点澄清。我希望这使它更容易理解。否则,我明天再看一遍,现在对我来说已经很晚了。
n314159

“两个生命周期重叠的非位域对象,如果一个嵌套在另一个对象中,或者至少一个是零大小的子对象,并且它们是不同类型,则它们可能具有相同的地址。” 2个生命周期重叠的对象,相同的类型,具有相同的地址
语言律师

不好意思,你能说清楚吗?您引用了我的答案中的标准报价,并举了一个与此稍有冲突的示例。我不确定这是否是对我的回答的评论,以及它是否应该告诉我。关于您的示例,我想说的是,人们还必须考虑标准的其他部分(有一段关于无符号字符数组为另一个对象提供存储的内容,有关零大小基本优化的内容,还有一点还应该看看是否有新的placement具有特殊津贴,我认为与OP无关的所有事项)
n314159

@ n314159我认为此措辞可能有误。
语言律师
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.