考虑以下示例:
template <class T>
struct A {
[[no_unique_address]] T t;
int i;
};
struct B {
long l;
int i;
};
class C {
long l;
int i;
};
GCC 和 Clang 都认为
sizeof(A<B>)
是 24
,而 sizeof(A<C>)
是 16
。 编译器资源管理器
类模板
A<T>
将 T
作为具有 [[no_unique_address]]
属性的数据成员之一。 B
和 C
之间的唯一区别是 B
是 struct
,而 C
是 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;
};
所以看来差异不是来自
struct
和class
,而是来自数据成员的可访问性。
我知道编译器是否将其他成员嵌入到具有
[[no_unique_address]]
属性的成员的尾部填充中是实现定义的,但我猜有一些特殊规则会导致 GCC 和 Clang 中出现相同的奇怪行为。我检查了标准和 ABI 文档,但找不到描述。
这是因为 ABI 指定布局的方式。
https://itanium-cxx-abi.github.io/cxx-abi/abi.html#pod:
这些类型的dsize、nvsize 和 nvalign 被定义为它们的普通大小和对齐方式。这些属性仅对用作基类的非空类类型重要。我们忽略 POD 的尾部填充,因为解决 CWG 问题 43 之前的标准不允许我们将其用于其他任何用途,而且它有时允许更快地复制类型。
dsize是对象的“数据大小”,这意味着该类型使用了多少字节,不包括尾部填充。
这意味着对于“用于布局目的的 POD”类型,可能的尾部填充被视为该类型数据的一部分,因此不能重用来保存成员i
。当你将任何一个成员设为私有时,它就不再是“为了布局而使用的 POD”,因此
dsize 变成了 sizeof(long) + sizeof(int)
(而不是
sizeof(B) = 2 * sizeof(long)
),并且可以将下一个成员
i
放置在尾部填充。如果您尝试创建基类子对象,也会出现同样的问题:
template <class T>
struct A : T {
int i;
};
struct B {
long l;
int i;
}; // sizeof = 16, dsize = 16
class C {
long l;
int i;
}; // sizeof = 16, dsize = 12
// A<B>: 16 bytes B, 4 bytes for int i, 4 bytes for padding; sizeof = 24, dsize = 20
// A<C>: 12 bytes C, 4 bytes for int i, no padding; sizeof = 16, dsize = 16
// (The same numbers for `[[no_unique_address]] T t;`)