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

时间:2019-04-24 06:45:28

标签: c endianness bit-fields

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

例如,我们有一块1.5字节的数据,因此我们使用了struct:

{
    uint8_t data1; // lsb
    uint8_t data2:4; // msb
    uint8_t reserved:4;
} Data;

保留位始终为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

我们之所以不首先执行此操作,是因为许多数据字段小于1个字节,而不是大于1个字节-这意味着通常对于位字段,我们不必这样做任何遮罩和移位,因此后处理更加简单。

1 个答案:

答案 0 :(得分:3)

  

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

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

  

无论字节顺序如何,data1总是代表期望的正确值。

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

  

data2和reserved是错误的方法吗,data2代表高4位而不是低4位?

是的。它们也可以在不同的字节上。同样,编译器不必为位域支持uint8_t,即使它另外支持类型也是如此。

  

(字节)字节顺序通常取决于字节的字节顺序吗?

最低有效位将始终位于最低有效字节中,但无法在C 中确定该位将在字节中的何处。

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

另一方面,位字段定义太差,无法通过定义的字段顺序确定位的顺序。

  

是由硬件还是由编译器确定的位序?

编译器指示数据类型如何映射到实际位,但是硬件会对其产生重大影响。对于位域,相同硬件的两个不同的编译器可以按不同的顺序放置域。

  

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

不是。如果可能的话,取决于编译器的操作方式。

  

尽管就代码而言,位域是最巧妙的方式来映射传入的数据,但我想我只是想知道,放弃它们是否更安全,使用类似的东西:

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

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

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

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

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

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