nil*_*ilo 4 c c++ arm padding abi
在C99标准告诉我们:
结构对象内可能有未命名的填充,但不是在其开头。
和
在结构或联合的末尾可能有未命名的填充。
我假设这也适用于任何 C++ 标准,但我没有检查它们。
让我们假设在 ARM Cortex-M 上运行的 C/C++ 应用程序(即在应用程序中使用两种语言)将一些持久数据存储在本地介质(例如串行 NOR 闪存芯片)上,并在上电后读取它循环,可能在将来升级应用程序本身之后。升级后的应用程序可能是用升级后的编译器(我们假设是 gcc)编译的。
让我们进一步假设开发人员很懒惰(当然不是我),并且直接将一些普通的 C 或 C++ 流式传输struct到闪存,而不是像任何偏执的有经验的开发人员那样首先将它们序列化。
事实上,有问题的开发人员很懒惰,但并非完全无知,因为他阅读了AAPCS(Arm 架构的过程调用标准)。
除了懒惰之外,他的理由如下:
structs 以避免在应用程序的其余部分出现未对齐问题。offsetof和 total sizeof)完全struct由 AAPCS为任何 C 或 C++ 确定。不过,开发者是有良心的,他有点担心:
我的问题是:那个懒惰的开发人员的生活有多危险?换句话说,struct在上述假设下,填充在 C/C++ 中的稳定性如何?
在提出这个问题两周后,收到的唯一答案并没有真正回答所提出的问题。我也在ARM 社区论坛上问过完全相同的问题,但根本没有答案。
然而,我选择接受3246135作为答案,因为:
我将没有正确答案视为与此案非常相关的信息。软件问题解决方案的正确性应该是显而易见的。在我的问题中所做的假设可能是正确的,但我无法轻易证明。此外,如果假设不正确,在一般情况下,后果可能是灾难性的。
相比风险,开发者在使用答案中暴露的策略时的负担似乎非常合理。假设一个恒定的字节序(这很容易强制执行),它是 100% 安全的(任何偏差都会在编译时产生错误),并且比完整的序列化轻得多。基本上,答案中公开的策略
是为了使 C/C++struct的持久性独立于任何 ABI 而必须付出的最低代价。
如果您是一名开发人员,问自己上述问题,请不要偷懒,而是使用已接受的答案中公开的策略,或保证跨软件版本的恒定填充的替代策略。
您永远无法 100% 确定编译器不会在某些容量中引入填充。但是,您可以通过遵循一些规则来降低风险:
uint32_t,int64_t等等。请注意,这样做可能会引入一些显式填充字段来满足对齐要求。
例如:
struct orig {
int a;
char b;
int c[10];
short d;
char e[15];
long f;
int g;
};
Run Code Online (Sandbox Code Playgroud)
假设sizeof(short) == 2、sizeof(int) == 4和sizeof(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];
};
Run Code Online (Sandbox Code Playgroud)
您的结构大小为 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];
};
Run Code Online (Sandbox Code Playgroud)
通过按大小降序对字段进行排序,我们基本上删除了字段之间的填充,只在末尾留下潜在的填充。还要注意使用固定大小以避免一些意外。然后作为保障,我们添加:
static_assert(sizeof(struct reordered) == 74);
Run Code Online (Sandbox Code Playgroud)
因此,如果结构的编译大小发生变化,您将在编译时知道。
有关更多详细信息,请查看结构包装的失落艺术。