出人意料地发现,clang++7 允许自己将派生结构的字段(在下面的示例中带有字段“B::u16_gap”的结构“B”)紧密打包到基本结构的对齐间隙(结构“A”)。但是示例中的函数“zero_init”期望它的输入是具有未使用对齐间隙的“A”对象,因此它可以被“memcpy”覆盖。当应用于“B”对象时,这个函数“zero_init”当然会覆盖“B::u16_gap”字段的任何值。
// compilation: clang++-7 -std=c++17 test.cpp
#include <cstdint>
#include <cstring>
#include <iostream>
struct A {
std::uint32_t u32 = 0;
std::uint16_t u16 = 0;
};
void zero_init(A& a) {
static constexpr A zero = {};
std::memcpy(&a, &zero, sizeof(A));
};
struct B: public A {
std::uint16_t u16_gap = 0;
};
static_assert(sizeof(A) == 8);
static_assert(sizeof(B) == 8); // clang++-7 packs additional field "B::u16_gap" to the alignment gap of the A (for g++-7 it is not true)
int main() {
B b;
A& a = b;
b.u16_gap = 123;
zero_init(a);
std::cout << b.u16_gap << std::endl; // writes "0" instead of expected "123"
}
此代码中格式错误的部分(偏离 C++17 标准的“良好行为”规则)在哪里?
最佳答案
如您的静态断言所示,A 的大小为 8 个字节。
这意味着您的静态“零”A 对象由 8 个字节的零组成。
memcpy
不知道类型,因此您将普通的 8 个字节复制到 a
的地址,从而覆盖 u16_gap
字段,即存储在这 8 个字节中,就像您的第二个断言显示的那样。
如果你想改变这个,你可以只复制 6 个字节,它应该可以工作。
关于c++ - 初始化派生结构的基础部分/意外打包派生结构字段到基础结构的对齐间隙,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52688933/