C/C++结构填充在AAPCS(ARM ABI)下有多稳定?

How stable is C/C++ structure padding under the AAPCS (ARM ABI)?

问题

C99 standard告诉我们:

There may be unnamed padding within a structure object, but not at its beginning.

There may be unnamed padding at the end of a structure or union.

我假设这也适用于任何 C++ 标准,但我没有检查它们。

让我们假设 ARM Cortex-M 上的 C/C++ 应用程序(即应用程序中使用两种语言)运行 将在本地介质(串行 NOR - 例如闪存芯片),并在电源循环后读回,可能在将来升级应用程序本身之后。升级后的应用程序可能是用升级后的编译器编译的(我们假设是 gcc)。

让我们进一步假设开发人员是懒惰的(当然不是我),直接将一些纯 C 或 C++ struct 流式传输到闪存,而不是首先将它们序列化为任何 偏执狂有经验的开发人员会做。

其实题主是懒惰,但也不是完全无知,因为他看过AAPCS (Procedure Call Standard for the Arm Architecture).

除了懒惰之外,他的理由如下:

不过开发者有良心,有点担心:

我的问题是:那个懒惰的开发人员的生活有多危险?换句话说,在上述假设下 C/C++ structs 中的填充有多稳定?

结论

这个问题提出两周后,唯一的答案是 收到并没有真正回答所提出的问题。我也问过 完全相同的问题 on an ARM community forum, 但根本没有得到任何答复。

然而,我选择接受 作为答案,因为:

  1. 我认为没有正确答案是非常相关的信息 对于这种情况。软件问题解决方案的正确性 应该是显而易见的。我的问题中所做的假设可能是正确的, 但我不能轻易证明这一点。此外,如果假设是 不正确,在一般情况下,后果可能是 灾难性的。

  2. 相对于风险,开发者使用时的负担 中暴露的策略似乎 很合理。假设一个恒定的字节顺序(这很容易 强制执行),它是百分百安全的(任何偏差都会产生 编译时的错误)并且它比成熟的要轻得多 序列化。基本上,策略暴露在 是强制性最低要求 为了使 C/C++ struct 独立于任何 ABI 持久化而付出的代价。

如果您是一名开发人员,问自己上述问题,请回答 不要偷懒,而是使用接受的策略 答案,或 保证恒定填充的替代策略 跨软件版本.

您永远无法 100% 确定编译器不会以某种方式引入填充。但是,您可以通过遵循一些规则来降低风险:

  • 对所有成员使用固定大小的类型,即 uint32_tint64_t
  • 每个成员的起始偏移量是成员大小的倍数(或者如果成员是数组/结构,则为最大成员的大小)。
  • 避免位域

请注意,这样做可能会引入一些显式填充字段来满足对齐。

例如:

struct orig {
    int a;
    char b;
    int c[10];
    short d;
    char e[15];
    long f;
    int g;
};

假设 sizeof(short) == 2sizeof(int) == 4sizeof(long) == 8,此结构成员的大小为 74。如果考虑可能的填充:

struct orig_padded {
    int a;
    char b;
    char pad1[3];
    int c[10];
    short d;
    char e[15];
    char pad2[7];
    long f;
    int g;
    char pad3[4];
};

您的结构大小为 88。

通过一些重新排列,我们可以将尺寸缩小回 74:

struct reordered {
    int64_t f;
    int32_t a;
    int32_t c[10];
    int32_t g;
    int16_t d;
    char b;
    char e[15];
};

通过按大小降序排列字段,我们基本上删除了字段之间的填充,只在末尾留下潜在的填充。还要注意使用固定尺寸以避免一些意外。然后作为保障,我们添加:

static_assert(sizeof(struct reordered) == 74);

因此,如果结构的编译大小发生变化,您将在编译时知道。

有关详细信息,请查看 The Lost Art of Structure Packing