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)?
是否可能data2
是reserved
错误的方式,用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 个字节 - 这意味着通常对于位字段,我们不必进行任何屏蔽,并且移动,因此后处理更简单。
我应该使用位字段来映射传入的串行数据吗?
不会。位域有很多实现指定的行为,这使得使用它们成为一场噩梦。
无论字节序如何,data1 是否始终代表预期的正确值。
是的,但那是因为uint8_t
最小的可寻址单元:字节。对于较大的数据类型,您需要注意字节顺序。
难道data2和reserved是错误的方式,data2代表高4位而不是低4位?
是的。它们也可以位于不同的字节上。此外,编译器不必支持uint8_t
位域,即使它会支持该类型。
位字节顺序(通常)是否取决于字节字节顺序,或者它们可以完全不同吗?
最低有效位始终位于最低有效字节中,但在 C 中无法确定该位位于字节中的哪个位置。
位移运算符提供了足够好的顺序的可靠抽象:对于数据类型,对于所有编译器和所有体系uint8_t
结构,(1u << 0)
始终是最低有效位和(1u << 7)
最高有效位。
另一方面,位字段的定义非常糟糕,以至于您无法通过定义字段的顺序来确定位的顺序。
位字节顺序是由硬件还是编译器决定的?
编译器决定数据类型如何映射到实际位,但硬件对其影响很大。对于位字段,同一硬件的两个不同编译器可以按不同的顺序放置字段。
是否有一种简单的方法可以在编译器中确定它的方式,并在需要时保留位字段条目?
并不真地。如果可能的话,这取决于您的编译器如何执行此操作。
虽然位字段是映射传入数据的最简洁的代码方式,但我想我只是想知道放弃它们并使用类似的东西是否更安全:
绝对放弃位字段,但我也建议为此目的完全放弃结构,因为:
您需要使用编译器扩展或手动工作来处理字节顺序。
您需要使用编译器扩展来禁用填充,以避免由于对齐限制而出现间隙。这会影响某些系统上的成员访问性能。
您不能有可变宽度或可选字段。
如果您不知道这些问题,很容易出现严格的别名违规。如果为数据帧定义字节数组并将其转换为指向结构的指针,然后取消引用它,那么在很多情况下都会遇到问题。
相反,我建议手动执行此操作。定义字节数组,然后在必要时使用位移位和掩码将每个字段分开,手动将每个字段写入其中。您可以为基本数据类型编写简单的可重用转换函数。