我应该使用位字段来映射传入的串行数据吗?

ric*_*art 4 c endianness bit-fields

我们有通过串行(蓝牙)传入的数据,它映射到特定的结构。该结构的某些部分是子字节大小,因此“显而易见”的解决方案是将传入数据映射到位字段。我无法确定的是机器或编译器的位字节顺序是否会影响它(这很难测试),以及我是否应该完全放弃位字段。

例如,我们有一条1.5字节的数据,所以我们使用结构体:

{
    uint8_t data1; // lsb
    uint8_t data2:4; // msb
    uint8_t reserved:4;
} Data;
Run Code Online (Sandbox Code Playgroud)

保留位始终为 1

例如,如果传入的数据是 0xD2,0xF4,则值为 0x04D2,即 1234。

我们使用的结构始终适用于我们测试过的系统,但我们需要它尽可能可移植。

我的问题是:

  • data1无论字节序如何,总是会按预期表示正确的值(我假设是的,并且硬件/软件接口应该始终正确处理单个整个字节 - 如果发送 0xD2,则应该接收 0xD2)?

  • 是否可能data2reserved错误的方式,用data2代表高 4 位而不是低 4 位?

如果是:

  • 位字节顺序(通常)是否取决于字节字节顺序,或者它们可以完全不同吗?

  • 位字节顺序是由硬件还是编译器决定的?看起来 Intel 上的所有 Linux 系统都是一样的——对于 ARM 来说也是如此吗?(如果我们可以说我们可以支持所有 Intel 和 ARM linux 版本,那应该没问题)

  • 是否有一种简单的方法可以在编译器中确定它的方式,并在需要时保留位字段条目?

虽然位字段是映射传入数据的最简洁的代码方式,但我想我只是想知道放弃它们并使用类似的东西是否更安全:

struct {
    uint8_t data1; // lsb (0xFF)
    uint8_t data2; // msb (0x0F) & reserved (0xF0)
} Data;

Data d;

int value = (d.data2 & 0x0F) << 16 + d.data1
Run Code Online (Sandbox Code Playgroud)

我们之所以不这样做,首先是因为许多数据字段小于 1 个字节,而不是超过 1 个字节 - 这意味着通常对于位字段,我们不必进行任何屏蔽,并且移动,因此后处理更简单。

use*_*733 6

我应该使用位字段来映射传入的串行数据吗?

不会。位域有很多实现指定的行为,这使得使用它们成为一场噩梦。

无论字节序如何,data1 是否始终代表预期的正确值。

是的,但那是因为uint8_t最小的可寻址单元:字节。对于较大的数据类型,您需要注意字节顺序。

难道data2和reserved是错误的方式,data2代表高4位而不是低4位?

是的。它们也可以位于不同的字节上。此外,编译器不必支持uint8_t位域,即使它会支持该类型。

位字节顺序(通常)是否取决于字节字节顺序,或者它们可以完全不同吗?

最低有效位始终位于最低有效字节中,但在 C 中无法确定该位位于字节中的哪个位置

位移运算符提供了足够好的顺序的可靠抽象:对于数据类型,对于所有编译器和所有体系uint8_t结构,(1u << 0)始终是最低有效位和(1u << 7)最高有效位。

另一方面,位字段的定义非常糟糕,以至于您无法通过定义字段的顺序来确定位的顺序。

位字节顺序是由硬件还是编译器决定的?

编译器决定数据类型如何映射到实际位,但硬件对其影响很大。对于位字段,同一硬件的两个不同编译器可以按不同的顺序放置字段。

是否有一种简单的方法可以在编译器中确定它的方式,并在需要时保留位字段条目?

并不真地。如果可能的话,这取决于您的编译器如何执行此操作。

虽然位字段是映射传入数据的最简洁的代码方式,但我想我只是想知道放弃它们并使用类似的东西是否更安全:

绝对放弃位字段,但我也建议为此目的完全放弃结构,因为:

  • 您需要使用编译器扩展或手动工作来处理字节顺序。

  • 您需要使用编译器扩展来禁用填充,以避免由于对齐限制而出现间隙。这会影响某些系统上的成员访问性能。

  • 您不能有可变宽度或可选字段。

  • 如果您不知道这些问题,很容易出现严格的别名违规。如果为数据帧定义字节数组并将其转换为指向结构的指针,然后取消引用它,那么在很多情况下都会遇到问题。

相反,我建议手动执行此操作。定义字节数组,然后在必要时使用位移位和掩码将每个字段分开,手动将每个字段写入其中。您可以为基本数据类型编写简单的可重用转换函数。