需为哪个成员添加no_unique_address?为何仅对首个成员生效?
为什么
[[no_unique_address]]仅对第一个成员生效? 初始结构体定义
我们先看两个自定义类的定义:
class eight { int i; char c; eight(const blub&) {} }; class one { char s; one(const blob&) {} };
eight的大小为8字节(int占4字节,char占1字节,加上3字节填充以满足int的4字节对齐要求),one的大小为1字节。
未使用属性的结构体布局(example1)
当把这两个类嵌入到如下结构体时:
struct example1 { eight b0; one b1; };
sizeof(example1)的结果是12字节。原因是:b0和b1存储在非重叠内存中(总共9字节),而结构体的对齐要求由成员中最严格的对齐规则决定(这里eight要求4字节对齐),因此总大小需要向上舍入到12字节。
C++20的[[no_unique_address]]属性
C++20引入的[[no_unique_address]]属性,允许相邻成员共享内存地址,也明确允许利用一个成员的尾部填充存储其他成员。根据cppreference的描述:
表明该数据成员无需拥有与类中所有其他非静态数据成员不同的地址。这意味着如果成员是空类型(例如无状态分配器),编译器可将其优化为不占用空间,就像空基类一样。如果成员非空,其尾部填充也可被 reused 来存储其他数据成员。
不同成员添加属性的结果
为第一个成员添加属性(example2)
如果给第一个成员b0添加[[no_unique_address]]属性:
struct example2 { [[no_unique_address]] eight b0; one b1; };
此时sizeof(example2)降至8字节——编译器可以把b1放在b0的尾部填充区域(即b0中char之后的3字节空白处),这样整个结构体的大小就等于b0的大小,同时满足对齐要求。
为第二个成员添加属性(example3)
但如果给第二个成员b1添加该属性:
struct example3 { eight b0; [[no_unique_address]] one b1; };
sizeof(example3)仍然是12字节,这和直觉预期不符——为什么只有给第一个成员加属性才有效?
原因解析
核心原因在于结构体成员的布局顺序规则和[[no_unique_address]]的作用逻辑:
- 结构体成员必须按声明顺序布局,第一个成员
b0始终从结构体的起始地址(偏移0)开始存放,它的尾部填充是自身内部的空间(b0的char之后的3字节)。 [[no_unique_address]]的作用是允许其他成员复用当前成员的尾部填充,或者当前成员复用其他成员的填充/地址:- 当给
b0加属性时,规则允许后续的b1复用b0的尾部填充,因此b1可以放在b0内部的空白区域,不需要额外占用空间,最终结构体大小等于b0的大小。 - 当给
b1加属性时,它只能允许自己复用其他成员的填充或地址,但b0是第一个成员,必须占据偏移0到7的空间,b1无法和非空的b0重叠(空类型才允许完全重叠),只能放在b0之后的位置(偏移8)。此时b0的尾部填充属于b0自身的内部空间,b1作为后续成员,无法反向复用前面成员的填充——除非前面的成员也声明了[[no_unique_address]],允许后续成员使用自己的填充。
- 当给
- 此外,结构体的对齐要求由最严格的成员对齐规则决定(这里是
eight的4字节对齐),即使b1放在偏移8的位置,总大小9字节仍需向上舍入到12字节以满足对齐要求。
内容的提问来源于stack exchange,提问作者BeeOnRope
相关产品推荐
相关产品推荐

