打包结构是可移植的吗?

新手上路,请多包涵

我在 Cortex-M4 微控制器上有一些代码,想使用二进制协议与 PC 通信。目前,我正在使用使用 GCC 特定的 packed 属性的打包结构。

这是一个粗略的大纲:

 struct Sensor1Telemetry {
    int16_t temperature;
    uint32_t timestamp;
    uint16_t voltageMv;
    // etc...
} __attribute__((__packed__));

struct TelemetryPacket {
    Sensor1Telemetry tele1;
    Sensor2Telemetry tele2;
    // etc...
} __attribute__((__packed__));

我的问题是:

  • 假设我对 MCU 和客户端应用程序上的 TelemetryPacket 结构使用完全相同的定义,上述代码是否可以跨多个平台移植? (我对 x86 和 x86_64 感兴趣,需要它在 Windows、Linux 和 OS X 上运行。)
  • 其他编译器是否支持具有相同内存布局的打包结构?用什么语法?

编辑

  • 是的,我知道打包结构是非标准的,但它们似乎很有用,可以考虑使用它们。
  • 我对 C 和 C++ 都感兴趣,尽管我认为 GCC 不会以不同的方式处理它们。
  • 这些结构不是继承的,也不会继承任何东西。
  • 这些结构仅包含固定大小的整数字段,以及其他类似的打包结构。 (我以前被花车烧过……)

原文由 Venemo 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 459
2 个回答

你永远不应该在编译域中使用结构体,而不是内存(硬件寄存器,从文件中分离读取的项目或在处理器或同一处理器不同软件(应用程序和内核驱动程序之间)之间传递数据)。你是在自找麻烦,因为编译器有一定的自由意志来选择对齐,然后用户可以通过使用修饰符使情况变得更糟。

不,没有理由假设您可以跨平台安全地执行此操作,即使您使用相同的 gcc 编译器版本,例如针对不同的目标(编译器的不同构建以及目标差异)。

为了降低失败的几率,首先从最大的项目开始(64 位,然后是 32 位,16 位,最后是任何 8 位项目)理想情况下,至少对齐 32 位,也许是 64,这是希望 arm 和 x86 做的,但这总是可以改变为以及从源代码构建编译器的任何人都可以修改默认值。

现在,如果这是一项工作安全问题,请确保继续,您可以对此代码进行定期维护,可能需要为每个目标定义每个结构(因此,ARM 结构定义的源代码副本和另一个对于 x86,或者如果不是立即需要,最终将需要它)。然后每一个或每几个产品发布,你都会被叫来处理代码……漂亮的小维护定时炸弹会爆炸……

如果您想在相同或不同架构的编译域或处理器之间安全地进行通信,请使用某种大小的数组、字节流、半字流或字流。显着降低故障和维护的风险。不要使用结构来分离那些只会恢复风险和失败的项目。

人们似乎认为这没问题的原因是因为您对相同的目标或系列(或从其他编译器选择派生的编译器)使用相同的编译器或系列,因为您了解语言的规则以及实现定义的区域在哪里最终会遇到不同,有时你的职业生涯需要几十年,有时需要几周……这是“在我的机器上工作”的问题……

原文由 old_timer 发布,翻译遵循 CC BY-SA 3.0 许可协议

考虑到上述平台,是的,打包结构完全可以使用。 x86 和 x86_64 始终支持非对齐访问,并且与普遍看法相反,这些平台上的非对齐访问在很长一段时间内( 几乎)具有与对齐访问相同的速度(不存在未对齐访问慢得多的情况)。唯一的缺点是访问可能不是原子的,但我认为在这种情况下并不重要。并且编译器之间有一个协议,打包的结构将使用相同的布局。

GCC/clang 支持使用您提到的语法的打包结构。 MSVC 有 #pragma pack ,可以这样使用:

 #pragma pack(push, 1)
struct Sensor1Telemetry {
    int16_t temperature;
    uint32_t timestamp;
    uint16_t voltageMv;
    // etc...
};
#pragma pack(pop)

可能会出现两个问题:

  1. 跨平台的字节序必须相同(您的 MCU 必须使用 little-endian)
  2. 如果您将指针分配给压缩结构成员,并且您使用的架构不支持未对齐访问(或使用具有对齐要求的指令,例如 movapsldrd ),那么您可能会使用该指针导致崩溃(gcc 不会对此发出警告,但 clang 会发出警告)。

这是来自 GCC 的文档:

打包属性指定变量或结构字段应具有最小可能的对齐方式 - 变量一个字节

所以 GCC 保证 不会使用任何填充。

MSVC:

打包一个类就是将它的成员直接放在内存中

所以 MSVC 保证 不会使用任何填充。

我发现的唯一“危险”区域是位域的使用。那么 GCC 和 MSVC 之间的布局可能会有所不同。但是,GCC 中有一个选项,使它们兼容: -mms-bitfields


提示:即使这个解决方案现在可以工作,而且它不太可能停止工作,我建议你保持你的代码对这个解决方案的依赖性低。

注意:我在这个答案中只考虑了 GCC、clang 和 MSVC。可能有编译器,但这些事情不是真的。

原文由 geza 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题