非多态 C++ class 的 sizeof 如何大于其成员的 sizeof 之和?
How sizeof a not-polymorphic C++ class can be larger than the summed sizeof of its members?
在下面的例子中,struct E
继承了 struct C
和 D
并且没有其他数据成员:
struct A{};
struct B{};
struct C : A, B {};
struct D : A, B {};
struct E : C, D {};
int main() {
static_assert(sizeof(C) == 1);
static_assert(sizeof(D) == 1);
//static_assert(sizeof(E) == 2); // in Clang and GCC
static_assert(sizeof(E) == 3); //in MSVC
}
在我测试过的所有编译器中,sizeof(C)==1
和 sizeof(D)==1
,并且仅在 MSVC sizeof(E)==3
中超过其 parents/members 的总大小,演示:https://gcc.godbolt.org/z/aEK7rjKcW
实际上我希望找到 sizeof(E) <= sizeof(C)+sizeof(D)
(以防空碱基优化)。而且这里几乎没有任何填充,否则 sizeof(E)
将是 2 或 4。
E
中额外的 space (sizeof(E)-sizeof(C)-sizeof(D) == 1
) 的目的是什么?
首先,由于填充和对齐,它可能大于子对象的总和。但是,您可能已经意识到这一点,而这不是您要问的。
要确定您的布局,您可以使用以下代码打印所有子对象的偏移量(及其类型的大小):
static E x;
int main() {
E *e = &x;
C *c = e;
D *d = e;
A *ca = c, *da = d;
B *cb = c, *db = d;
#define OFF(p) printf(#p " %d %d\n", (int)((char*)p - (char*)e), (int)sizeof(*p))
OFF(e);
OFF(c);
OFF(ca);
OFF(cb);
OFF(d);
OFF(da);
OFF(db);
}
gcc/clang 的输出是:
e 0 2
c 0 1
ca 0 1
cb 0 1
d 1 1
da 1 1
db 1 1
MSVC 的输出是:
e 0 3
c 0 1
ca 0 1
cb 1 1
d 2 1
da 2 1
db 3 1
这表明MSVC实现EBO的方式与其他编译器不同。特别是,它没有将 A
和 B
放在 C
内的同一地址和 D
内的同一地址(就像其他编译器所做的那样),而是将它们放在不同的偏移量。然后,即使sizeof(C) == 1
,当它是一个子对象时,它也会为它分配完整的两个字节。这样做很可能是为了避免 cb
将其他一些 B
从另一个子对象中别名化,即使在这种情况下这不是问题。
在下面的例子中,struct E
继承了 struct C
和 D
并且没有其他数据成员:
struct A{};
struct B{};
struct C : A, B {};
struct D : A, B {};
struct E : C, D {};
int main() {
static_assert(sizeof(C) == 1);
static_assert(sizeof(D) == 1);
//static_assert(sizeof(E) == 2); // in Clang and GCC
static_assert(sizeof(E) == 3); //in MSVC
}
在我测试过的所有编译器中,sizeof(C)==1
和 sizeof(D)==1
,并且仅在 MSVC sizeof(E)==3
中超过其 parents/members 的总大小,演示:https://gcc.godbolt.org/z/aEK7rjKcW
实际上我希望找到 sizeof(E) <= sizeof(C)+sizeof(D)
(以防空碱基优化)。而且这里几乎没有任何填充,否则 sizeof(E)
将是 2 或 4。
E
中额外的 space (sizeof(E)-sizeof(C)-sizeof(D) == 1
) 的目的是什么?
首先,由于填充和对齐,它可能大于子对象的总和。但是,您可能已经意识到这一点,而这不是您要问的。
要确定您的布局,您可以使用以下代码打印所有子对象的偏移量(及其类型的大小):
static E x;
int main() {
E *e = &x;
C *c = e;
D *d = e;
A *ca = c, *da = d;
B *cb = c, *db = d;
#define OFF(p) printf(#p " %d %d\n", (int)((char*)p - (char*)e), (int)sizeof(*p))
OFF(e);
OFF(c);
OFF(ca);
OFF(cb);
OFF(d);
OFF(da);
OFF(db);
}
gcc/clang 的输出是:
e 0 2
c 0 1
ca 0 1
cb 0 1
d 1 1
da 1 1
db 1 1
MSVC 的输出是:
e 0 3
c 0 1
ca 0 1
cb 1 1
d 2 1
da 2 1
db 3 1
这表明MSVC实现EBO的方式与其他编译器不同。特别是,它没有将 A
和 B
放在 C
内的同一地址和 D
内的同一地址(就像其他编译器所做的那样),而是将它们放在不同的偏移量。然后,即使sizeof(C) == 1
,当它是一个子对象时,它也会为它分配完整的两个字节。这样做很可能是为了避免 cb
将其他一些 B
从另一个子对象中别名化,即使在这种情况下这不是问题。