问题
C99标准告诉我们:
结构体对象内可以有未命名填充位,但不可以在其开头。
以及
结构体或联合体的末尾可以有未命名填充位。
我假设这同样适用于任何 C++ 标准,但我没有检查过。
让我们假设一个运行在 ARM Cortex-M 上的 C/C++ 应用程序(即应用程序中使用了两种语言)将一些持久数据存储在本地介质上(例如串行 NOR-flash 芯片),并在断电后读取它,可能会在将来升级应用程序之后进行读取。升级后的应用程序可能已经使用升级后的编译器进行了编译(我们假设使用 gcc)。
让我们进一步假设开发者很懒(当然不是我),直接将一些纯 C 或 C++ 的 struct
流式传输到闪存中,而不是像任何 多疑 经验丰富的开发者那样首先对它们进行序列化。
事实上,所涉及的开发者很懒,但不是完全无知,因为他已经阅读了 AAPCS(Arm 架构过程调用标准)。
他的理由除了懒惰之外还有:
- 他不想压缩
struct
以避免在应用程序的其余部分中出现对齐问题。 - AAPCS 为每种基本数据类型指定了固定的对齐方式。
- 填充的唯一合理动机是实现适当的对齐。
- 因此,他认为 AAPCS 完全确定了任何 C 或 C++ 的
struct
的填充(因此包括成员offsetof
和总sizeof
)。 - 因此,他进一步推断,只要闪存中数据的偏移量在写入和读取之间没有改变,我的应用程序就没有任何理由无法解释某个早期版本的同一应用程序写入的一些读回数据。
然而,这位开发者还有良心,有点担心:
- C 标准没有提到填充的任何理由。实现适当的对齐可能是填充的唯一合理理由,但编译器可以在标准规定范围内自由地进行填充。
- 他如何确信他的编译器真正遵循了 AAPCS?
- 他的假设是否会被他开始使用的某些看似不相关的编译器标志或编译器升级突然打破?
结论:
在这个问题被提出两周后,唯一得到的答案并没有真正回答所提出的问题。我还在ARM社区论坛上提出了完全相同的问题,但是没有得到任何答案。
然而,我选择接受3246135作为答案,因为:
1. 我认为对于这种情况来说,缺乏正确答案的存在本身就是非常重要的信息。解决软件问题的正确性应该是显而易见的。我所做的假设可能是正确的,但我不能轻易地证明它们的正确性。此外,如果这些假设是不正确的,在一般情况下其后果可能是灾难性的。 2. 相比风险,使用答案中提出的策略对开发者的负担似乎非常合理。假设端序是不变的(这很容易实现),它是百分之百安全的(任何偏差都会在编译时生成错误),而且比完整的序列化要轻得多。基本上,答案中提出的策略是为了使人们的C/C++结构体可以独立于任何ABI而持久化而必须付出的最低代价。
如果您是一个开发者,并且正在思考上述问题,请不要懒惰,使用接受的答案中所提出的策略或其他能够保证软件发布间填充字段不变的策略。
<stdint.h>
中的类型(至少在紧凑结构中),以便编译器不能更改大小。 - user3386109