gpt4 book ai didi

c++ - 标准布局和尾部填充

转载 作者:IT老高 更新时间:2023-10-28 21:53:31 26 4
gpt4 key购买 nike

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);

您可以检查布局 in clang on godbolt并看到大小改变的原因是在 FooBefore 中,成员 value 放置在偏移量 16 处(保持与 FooBeforeBase 的完全对齐 8 >) 而在 FooAfter 中,成员 value 放置在偏移量 12 处(有效地使用 FooAfterBase 的尾部填充)。

我很清楚 FooBeforeBase 是标准布局,但 FooAfterBase 不是(因为它的非静态数据成员并不都具有相同的访问控制, [class.prop]/3)。但是,FooBeforeBase 的标准布局需要这种填充字节,这是怎么回事?

gcc 和 clang 都重用 FooAfterBase 的填充,以 sizeof(FooAfter) == 16 结束。但 MSVC 没有,以 24 结尾。是否有符合标准要求的布局,如果没有,为什么 gcc 和 clang 会这样做?


有一些困惑,所以只是为了澄清:

  • FooBeforeBase 是标准布局
  • FooBeforenot(它和基类都有非静态数据成员,类似于 this example 中的 E)
  • FooAfterBasenot(它具有不同访问权限的非静态数据成员)
  • FooAfternot(出于上述两个原因)

最佳答案

这个问题的答案不是来自标准,而是来自 Itanium ABI(这就是为什么 gcc 和 clang 有一种行为而 msvc 做其他事情的原因)。该 ABI 定义了 a layout ,就本问题而言,其相关部分是:

For purposes internal to the specification, we also specify:

  • dsize(O): the data size of an object, which is the size of O without tail padding.

We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.

虚拟基类以外的成员的放置定义为:

Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless [... not relevant ...].

术语 POD 已从 C++ 标准中消失,但它意味着标准布局和可轻松复制。在这个问题中,FooBeforeBase 是一个 POD。 Itanium ABI 忽略尾部填充 - 因此 dsize(FooBeforeBase) 为 16。

但是 FooAfterBase 不是 POD(它可以简单地复制,但它是 not 标准布局)。结果,尾部填充没有被忽略,所以 dsize(FooAfterBase) 只是 12,而 float 可以直接到那里。

正如 Quuxplusone 在 related answer 中指出的那样,这会产生有趣的结果。 , 实现者通常还假设尾部填充没有被重用,这对这个例子造成了严重破坏:

#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
}

这里,= 做了正确的事(它不会覆盖 B 的尾部填充),但是 copy() 有一个库简化为 memmove() 的优化 - 它不关心尾部填充,因为它假设它不存在。

关于c++ - 标准布局和尾部填充,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53837373/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com