首页 > 解决方案 > 什么可以防止类中相邻成员的重叠?

问题描述

考虑以下三个structs:

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  

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

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

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

确实,如果我们在 上使用这个属性blub b0,则 的大小会bla下降到8,所以blob确实存储在blub Godbolt 上所见的 中

最后,我们来回答我的问题:

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

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


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

2这就是为什么我定义了复制构造函数:to make bluband blobnot trivially copyable

标签: c++language-lawyerc++20

解决方案


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

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

类型对象的对象表示是类型对象占用的对象T序列,其中等于。类型对象的值表示是参与表示类型值的一组位。对象表示中不属于值表示的位是填充位。N unsigned charTNsizeof(T)TT

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

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

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

[...]

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

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

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

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

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

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

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

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

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


1我忍不住添加了一个引用可能意味着占用:Webster's Revised Unabridged Dictionary, G. & C. Merriam, 1913(强调我的)

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

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


推荐阅读