David Hollman 最近在推特上发布了以下示例(我对其进行了稍微简化):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
您可以检查布局在上帝螺栓上叮当作响看到大小改变的原因是FooBefore
, 会员value
放置在偏移量 16 处(保持 8 的完全对齐)FooBeforeBase
)而在FooAfter
, 会员value
放置在偏移量 12 处(有效地使用FooAfterBase
的尾部填充)。
我很清楚FooBeforeBase
是标准布局,但是FooAfterBase
不是(因为它的非静态数据成员并不都具有相同的访问控制,[class.prop]/3)。但这是关于什么的FooBeforeBase
标准布局需要这种填充字节吗?
gcc 和 clang 都重用FooAfterBase
的填充,最终为sizeof(FooAfter) == 16
。但 MSVC 没有,最终为 24。标准是否有必需的布局,如果没有,为什么 gcc 和 clang 会这样做?
有一些混乱,所以只是澄清一下:
-
FooBeforeBase
是标准布局
-
FooBefore
is not(它和基类都有非静态数据成员,类似于E
in 这个例子)
-
FooAfterBase
is not(它具有不同访问权限的非静态数据成员)
-
FooAfter
is not(由于上述两个原因)
这个问题的答案不是来自标准,而是来自 Itanium ABI(这就是为什么 gcc 和 clang 有一种行为而 msvc 有其他行为)。 ABI 定义a layout,就本问题而言,其相关部分是:
出于规范内部的目的,我们还指定:
-
dsize(O):数据大小一个对象的大小,是没有尾部填充的 O 的大小。
and
我们忽略 POD 的尾部填充,因为该标准的早期版本不允许我们将其用于其他任何用途,而且它有时允许更快地复制类型。
其中除虚拟基类之外的成员的放置定义为:
从偏移量 dsize(C) 开始,如果需要与基类的 nvalign(D) 对齐或与数据成员的align(D) 对齐,则递增。将 D 放置在此偏移处,除非 [...不相关...]。
术语 POD 已从 C++ 标准中消失,但它意味着标准布局和普通可复制。在这个问题中,FooBeforeBase
是一个POD。安腾 ABI 忽略尾部填充 - 因此dsize(FooBeforeBase)
is 16.
But FooAfterBase
不是 POD(它是可以简单复制的,但它是not标准布局)。结果,尾部填充不会被忽略,所以dsize(FooAfterBase)
才 12 岁,而且float
可以去那里。
正如 Quuxplusone 在一篇文章中指出的,这会产生有趣的后果相关答案,实现者通常还假设尾部填充不被重用,这对这个示例造成了严重破坏:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %d\n", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %d\n", int(c1.m_c)); // 4
printf("before std::copy: %d\n", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %d\n", int(c1.m_c)); // 64, or 0, or anything but 4
}
Here, =
做正确的事(它不会覆盖B
的尾部填充),但是copy()
有一个库优化可以减少到memmove()
- 它不关心尾部填充,因为它假设它不存在。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)