【问题标题】:Initializing base part of the derived struct / Unexpected packing derived struct fields to alignment gap of the base struct初始化派生结构的基础部分/意外打包派生结构字段到基础结构的对齐间隙
【发布时间】:2019-03-12 08:06:06
【问题描述】:

意外地发现,clang++7 允许自己将派生结构的字段(下例中的结构“B”与字段“B::u16_gap”)紧密打包到基结构的对齐间隙(结构“A”)。但是示例中的函数“zero_init”期望它的输入是具有未使用对齐间隙的“A”对象,因此它可以被“mem​​cpy”覆盖。当应用于“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 标准的“良好行为”规则)在哪里?

【问题讨论】:

  • 你为什么期望 123?之后你将它归零。
  • 因为“zero_init”应该只触及“B”对象的“A”部分。它合法地(乍一看)只写入“A”基结构区域。
  • 建议您添加 [language-lawyer] 标签,因为这看起来很奇怪。您的测试表明结构的AB 部分将具有相同的地址,我认为这是不正确的标准

标签: c++ inheritance alignment language-lawyer


【解决方案1】:

就像您的静态断言显示的那样,A 的大小是 8 个字节。 这意味着您的静态“零”A-Object 由 8 个字节的零组成。 memcpy 不知道类型,因此您将纯 8 个字节复制到 a 的地址,从而覆盖存储在这 8 个字节中的 u16_gap 字段,就像您的第二个断言显示的那样。

如果你想改变它,你可以复制 6 个字节,它应该可以工作。

【讨论】:

  • 但是像“zero_init”这样的函数如何知道,禁止写入对齐间隙?据我所知,对齐间隙可以写成任何值。
  • 它不知道这是禁止的,这是你的功能。它只复制 8 个字节,因为这是 A 的大小(带有对齐)。
  • 目前,我最好的猜测是:由于 struct "A" 和 "B" 不是 POD(是真的吗?),所有操作(构造、赋值等) ) 只能通过非常精确的函数、非接触对齐间隙来执行。
  • 它们不是 POD,但我建议使用构造函数或类似的东西,而不是手动处理内存,因为在这种情况下这是不安全的
  • 谢谢。可以得出结论:类似 memcpy 的处理仅适用于 POD,其中“现代”非 POD 只能由其合法的构造函数和赋值运算符处理。即使它们看起来像 POD。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-20
  • 1970-01-01
  • 2022-01-21
  • 2021-11-15
相关资源
最近更新 更多