C++基类与[[no_unique_address]]成员的填充能否存储其他子对象?
关于[[no_unique_address]]与潜在重叠子对象的规则说明
规范层面的结论
非空的潜在重叠子对象在规范设计本意上是允许重叠的,你观察到的标准文本矛盾是提案落地时的笔误导致的。
- [dcl.attr.nouniqueaddr]的条款注释和引入该属性的P0840R2提案设计目标完全一致:被
[[no_unique_address]]标记的非静态数据成员、默认属于潜在重叠类别的基类子对象,都允许复用自身尾部的填充字节作为其他子对象的存储空间,这个规则从来没有限定仅空类型适用。 - [intro.object]/10中“仅零大小子对象可与其他生命周期重叠的对象共享地址”的表述是文本疏漏:该条款的本意是禁止两个非嵌套对象的有效存储字节相交,而非禁止对象地址范围内的填充区域被其他子对象占用。以测试用例为例:
struct A包含4字节int、2字节short成员,在常规32/64位平台对齐规则下尾部有2字节填充,这部分填充不属于A的有效存储,完全可以被后续仅含2字节short成员的struct B子对象使用,这种场景下两个子对象的有效存储完全不相交,仅A的尾部填充被复用,根本不违反“不同对象存储不相交”的核心规则。 - [basic.types.general]/2将所有潜在重叠子对象排除在平凡复制的字节拷贝范围外,恰恰印证了非空场景重叠的合法性:如果潜在重叠子对象的尾部填充可能被其他成员占用,直接按
sizeof(T)长度复制子对象会覆盖填充区存储的其他成员数据,这个规则从设计之初就不是仅为空类优化准备的。
测试代码如下,三大编译器的运行结果确实符合观察:
struct A { int x; short y; }; struct B { short b; }; struct C : A, B {}; struct D { [[no_unique_address]] A a; [[no_unique_address]] B b; }; // 以下断言可通过,但预期大小应为`sizeof(int) * 2` static_assert(sizeof(C) == sizeof(int) * 3); static_assert(sizeof(D) == sizeof(int) * 3);
编译器实现现状与优化可行性
目前GCC、Clang、MSVC均未实现非空潜在重叠子对象的尾部填充复用优化,这属于编译器实现质量(QoI)范畴的缺失,不是标准禁止该行为。
[[no_unique_address]]本身是优化许可标记,而非强制优化指令:标准从未要求编译器必须对标记了该属性的成员做地址重叠优化,哪怕是空类型成员,编译器也有权选择不做空类优化,更不用说实现复杂度更高的非空类型尾部填充复用。- 不存在无需依赖编译器扩展就能强制编译器实现该优化的方法。如果需要压缩结构体大小,目前只能通过手动调整成员排列顺序、按对齐值从小到大排列成员的方式尽可能减少显式填充,没有标准层面的语法可以强制触发非空子对象的尾部填充复用。
目前C++标准委员会已经确认了[intro.object]/10的文本错误,后续的标准修订版本会修正相关表述,消除“仅零大小子对象允许重叠”的歧义。
内容的提问来源于stack exchange,提问作者HolyBlackCat
相关产品推荐
相关产品推荐

