为什么 [[no_unique_address]] 对公共数据成员没有影响?

Plu*_*uto 6 c++ layout c++20

考虑以下示例:

template <class T>
struct A {
    [[no_unique_address]] T t;
    int i;
};

struct B {
    long l;
    int i;
};

class C {
    long l;
    int i;
};
Run Code Online (Sandbox Code Playgroud)

GCC 和 Clang 都认为sizeof(A<B>)24sizeof(A<C>)而是16编译器资源管理器

类模板A<T>将属性T作为其数据成员之一[[no_unique_address]]B和之间的唯一区别CB是 astructC是 a class。我不明白为什么A<B>,而且A<C>尺寸不一样。换句话说,为什么编译器将i类模板的成员嵌入A<T>到 的尾部填充中C而不是嵌入到 的尾部填充中B

如果我修改B成员访问权限private,GCC和Clang都认为的大小A<B>16:( Compiler Explorer )

template <class T>
struct A {
    [[no_unique_address]] T t;
    int i;
};

struct B {
private:      // private now
    long l;
    int i;
};

class C {
    long l;
    int i;
};
Run Code Online (Sandbox Code Playgroud)

所以看起来差异不是来自structclass,而是来自数据成员的可访问性。

我知道编译器是否将其他成员嵌入到具有该[[no_unique_address]]属性的成员的尾部填充中是实现定义的,但我猜有一些特殊规则会导致 GCC 和 Clang 中出现相同的奇怪行为。我检查了标准和 ABI 文档,但找不到描述。

Art*_*yer 3

这是因为 ABI 指定布局的方式。

https://itanium-cxx-abi.github.io/cxx-abi/abi.html#pod

这些类型的 dsize、nvsizenvalign定义为它们的普通大小和对齐方式。这些属性仅对用作基类的非空类类型重要。我们忽略 POD 的尾部填充,因为CWG 第 43 个问题解决之前的标准不允许我们将其用于其他用途,而且有时它允许更快地复制类型。

dsize是对象的“数据大小”,这意味着该类型使用了多少字节,不包括尾部填充。

这意味着对于“用于布局目的的 POD”类型,可能的尾部填充被视为该类型数据的一部分,因此不能重用来保存成员i(编译器将其视为B不透明的 16 字节,甚至虽然最后 4 个字节是填充的)

当您将任何成员设为私有时,它不再是“出于布局目的的 POD”,因此 dsize变为sizeof(long) + sizeof(int)而不是sizeof(B) = 2 * sizeof(long)),并且下一个成员i 可以放置在 tail-padding 中。

如果您尝试创建基类子对象,也会出现同样的问题。

#if BASE_CLASS
template <class T>
struct A : T {
    int i;
};
#else
struct A {
    [[no_unique_address]] T t;
    int i;
};
#endif

struct B {
    long l;
    int i;
};  // dsize = 16, nvalign = 8, sizeof = 16

class C {
    long l;
    int i;
};  // dsize = 12, nvalign = 8, sizeof = 16 (next multiple of 8)

// A<B>: 16 bytes B, 4 bytes for int i, 4 bytes for padding
// dsize = 20, sizeof = 24

// A<C>: 12 bytes C, 4 bytes for int i, no padding
// dsize = 16, sizeof = 16

// (The same numbers for `[[no_unique_address]] T t;`)
Run Code Online (Sandbox Code Playgroud)

正在引用的 CWG43 讨论了如何memcpy指定在 C++98 中的所有 POD 类型上工作。因此,布局这些类型的类必须在内部包含尾部填充,这样memcpy就不会覆盖任何本来会进入填充的数据。这在 C++03 中已修复,以指定“POD 类型的任何对象(基类子对象除外)T”,但布局规则未更改(大概是为了不破坏您可以使用memcpyPOD 类型的期望,即使他们是基类)。

[[no_unique_address]]被指定为与基类子对象(作为潜在重叠子对象)具有相同的行为,因此此行为是继承的。