打包结构是否可移植?

Ven*_*emo 40 c c++ gcc struct packed

我在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__));
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

编辑:

  • 是的,我知道打包的结构是非标准的,但它们看起来很有用,可以考虑使用它们.
  • 我对C和C++都感兴趣,虽然我不认为GCC会以不同的方式处理它们.
  • 这些结构不是继承的,不会继承任何东西.
  • 这些结构只包含固定大小的整数字段和其他类似的打包结构.(我之前被浮子烧了......)

gez*_*eza 25

考虑到上述平台,是的,打包结构完全可以使用.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)
Run Code Online (Sandbox Code Playgroud)

可能会出现两个问题:

  1. 跨平台的字节顺序必须相同(您的MCU必须使用little-endian)
  2. 如果你指定一个打包的struct成员的指针,并且你在一个不支持未对齐访问的架构上(或使用具有对齐要求的指令,如movapsldrd),那么你可能会使用该指针崩溃(gcc没有不要警告你这件事,但是铿锵有力.

以下是海湾合作委员会的文件:

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

所以GCC 保证不会使用填充.

MSVC:

打包类是将其成员直接放在内存中

所以MSVC 保证不会使用填充.

我发现的唯一"危险"区域是位域的使用.然后,GCC和MSVC之间的布局可能不同.但是,GCC中有一个选项,它使它们兼容:-mms-bitfields


提示:即使如果此解决方案现在可以正常工作,并且它不太可能停止工作,我建议您保持代码对此解决方案的依赖性较低.

注意:我在这个答案中只考虑了GCC,clang和MSVC.可能有编译器,但这些事情并非如此.

  • 如果未对齐访问不跨越缓存行边界,则它们甚至可以是原子访问 - 但对此几乎没有控制权 (2认同)
  • 我相信未对齐的访问仍然会有所作为:DIMM具有64位数据通道,对数据表中的信号数量进行了一些数学运算,似乎内存是以64位的倍数寻址的。因此,读取地址3的DWORD会从地址0读取64位(单次访问),而读取地址6的DWORD则会从地址0读取64位,而从地址8会读取64位(两次访问)。所有这些都无需考虑缓存子系统。 (2认同)
  • @AnttiHaapala:如果使用了压缩结构,那么编译器将不会使用这些指令。如果编译器不知道未对齐,唯一的问题。这就是我在2中描述的。 (2认同)

alk*_*alk 12

如果

  • 字节序不是问题
  • 两个编译器都正确处理打包
  • 两种C实现的类型定义都是准确的(符合标准).

然后是的," 打包结构 "是便携式的.

对于我的口味太多"如果",不要这样做.麻烦不值得出现.

  • 我会添加几个ifs:1)如果所有成员都有固定的大小,没有`int`,`short`,`long`,当然没有指针.2)没有位字段. (4认同)
  • @geza*实际上,使用打包的结构工作.如果字节顺序是一样的,他们的工作就像一个魅力,有没有麻烦的.*那是因为他们已经为你工作**到目前为止**?没有遇到任何问题***但***并不意味着他们"一般都像一个魅力". (4认同)
  • @DavidBowling:"*假设实现正确性*"据我记得,至少"打包"不是C标准的一部分,那么,"*正确*"虽然如此. (3认同)
  • @alk:*已使用固定大小.*OP只显示3个成员,我们只能猜出//等等背后的东西...... (2认同)
  • @Joshua您还要假设您在编译器中做出的任何选择都不会*将来*更改其实现,并且*部署平台也不会更改。我将使用一种更可移植,更稳定的方式来传递不基于“仅在此平台上使用此编译器执行此操作”的数据。因为总的来说,它“行不通”。固定宽度大小(例如,int16_t)实际上是*可选*,并且不是C标准所必需的。 (2认同)
  • 人们是不同的 :) 在这种情况下,我很乐意使用打包的结构。他们在过去 20 年里工作,在我看来,他们将在 20 年后工作。其他一些人会选择其他(在这种情况下,更复杂)的方式,只是因为,打包结构将来有可能无法使用。或者他们可能必须将程序移植到不支持它的编译器。我对此没有任何问题,正如我所说,人们是不同的,并为问题选择不同的解决方案:) (2认同)

baz*_*zza 8

您可以这样做,或使用更可靠的替代方案.

对于序列化狂热者中的核心,有CapnProto.这为您提供了一个本地结构来处理,并承诺确保当它通过网络传输并轻松处理时,它仍然有意义的另一端.称之为序列化几乎是不准确的; 它旨在尽可能地对结构的内存表示做一点.可能适合移植到M4

有Google协议缓冲区,这是二进制文件.更臃肿,但相当不错.随附的nanopb(更适合微控制器),但它没有完成整个GPB(我不认为它oneof).很多人虽然成功地使用它.

一些C asn1运行时足够小,可用于微控制器.我知道这个适合M0.

  • 我有点认为使用这些库从传感器传输一些小数据是矫枉过正的。 (2认同)
  • @geza,如果有人希望这个小传感器能够可靠地与运行在Windows,Linux和OSX上的软件进行通信,就像原始海报所要求的那样,而不必做一些无聊,容易出错的结构布局验证工作.使用专为工作设计的信誉良好的图书馆有助于避免自己完成所有工作.看看所有评论(包括你自己的)对于alk的优秀帖子; 字节序,布局,整数宽度; 在我看来,无聊,毫无意义的讨论; 这是一个解决的问题. (2认同)
  • 如果您使用带有 intXX_t 的打包结构,那么我绝对确定您不必执行诸如“布局验证”之类的任何操作。它将在提到的任何平台上开箱即用。在结构后放置一个 static_assert 来断言 `sizeof` 是可以的(以捕获将来可能出现的错误),仅此而已。 (2认同)

old*_*mer 8

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

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

为了降低失败的几率,首先要使用最大的项目(64位,然后是32位,16位,然后是最后的任何8位项目)理想情况下,最小值为32,最小可能为64,这可能是希望arm和x86做的,但总是可以改变为以及任何从源构建编译器的人都可以修改默认值.

现在,如果这是一个工作安全问题,请确保继续,您可以对此代码进行定期维护,可能需要为每个目标定义每个结构(因此,ARM的结构定义的源代码的一个副本和另一个对于x86,或者最终需要这个,如果没有立即).然后,您可以调用每个或每个产品版本来完成代码工作......很少的维护时间炸弹......

如果要在编译域或处理器之间安全地进行相同或不同的体系结构通信,请使用某种大小的数组,字节流,半字流或单词流.显着降低您的失败和维护风险.不要使用结构来挑选那些只是恢复风险和失败的物品.

人们似乎认为这是可以的,因为对相同的目标或系列(或从其他编译器选择派生的编译器)使用相同的编译器或系列,因为您了解语言的规则以及实现定义的区域在哪里最终会遇到差异,有时候你的职业生涯需要几十年的时间,有时需要数周才能完成......它的"在我的机器上工作"问题......