为什么比特位是位域的问题?
任何使用位域的便携式代码似乎都可以区分小端和大端平台。 在linux内核中查看struct iphdr的声明就是这样的代码的例子。 我不明白为什么比特序列是一个问题。
据我所知,位域是纯粹的编译器构造,用于方便位级操作。
例如,考虑下面的位域:
struct ParsedInt { unsigned int f1:1; unsigned int f2:3; unsigned int f3:4; }; uint8_t i; struct ParsedInt * d =&i;
在这里,写d->f2
只是一个简洁而可读的说法(i>>1) & (1<<4 - 1)
。
但是,位操作是精确定义的,而且不pipe架构如何。 那么,怎么来的位域是不可移植的?
按照C标准,编译器可以任意随意地存储位域。 你永远无法假设位的分配位置。 这里只是一些C标准没有规定的与位相关的东西:
未指定的行为
- 分配用于保存位域(6.7.2.1)的可寻址存储单元的alignment。
实现定义的行为
- 位域是否跨越存储单元边界(6.7.2.1)。
- 一个单元内的位域分配顺序(6.7.2.1)。
Big / Little Endian当然也是实现定义的。 这意味着你的结构可以用下面的方式分配(假设是16位):
PADDING : 8 f1 : 1 f2 : 3 f3 : 4 or PADDING : 8 f3 : 4 f2 : 3 f1 : 1 or f1 : 1 f2 : 3 f3 : 4 PADDING : 8 or f3 : 4 f2 : 3 f1 : 1 PADDING : 8
哪一个适用? 猜一猜,或者深入了解编译器的后端文档。 把32位整数的复杂度以大小写sorting。 然后添加这样一个事实,即编译器允许在位字段内的任何位置添加任意数量的填充字节 ,因为它被视为一个结构体(它不能在结构的最开始处添加填充,而是在其他地方)。
然后我甚至没有提到如果使用普通的“int”作为位域types=实现定义的行为,或者如果使用除(unsigned)int =实现定义的行为之外的任何其他types,会发生什么情况。
所以要回答这个问题,就不存在便携式位域代码这样的事情,因为C标准对于如何实现位域是非常模糊的。 唯一可以信任的位字段是布尔值块,程序员不关心内存中位的位置。
唯一的便携式解决scheme是使用按位运算符而不是位域。 生成的机器代码将完全相同,但具有确定性。 按位运算符可以在任何系统的任何C编译器上100%移植。
据我所知,位域是纯粹的编译器构造
这是问题的一部分。 如果位域的使用仅限于编译器所拥有的,那么编译器如何打包或者命令它们对于任何人来说都不是什么问题。
然而,比特字段可能被用来模拟编译器域外部的结构 – 硬件寄存器,通信的“线”协议或文件格式布局。 这些东西对位的布局有着严格的要求,使用位字段来模拟它们意味着你必须依赖实现定义,甚至更糟 – 编译器如何布局位域的非特定行为。
总之,位域没有被很好地指定,以使它们对于它们似乎最常用的情况是有用的。
ISO / IEC 9899: 6.7.2.1 / 10
一个实现可以分配任何足够大的可寻址存储单元来容纳一个比特字段。 如果有足够的空间,紧接在结构中的另一个比特字段之后的比特字段应被打包到相同单元的相邻比特中。 如果剩余空间不足,是否将不合适的位置放入下一个单元或者是否与相邻单元重叠是实现定义的。 在一个单元内(高位到低位或从低位到高位)的比特字段的分配顺序是实现定义的。 未指定可寻址存储单元的alignment方式。
在尝试编写可移植代码时,不考虑系统的字节顺序或位数,使用位移操作更安全,而不是对位字段sorting或alignment进行任何假设。
另见EXP11-C。 不要将希望一种types的操作符应用于不兼容types的数据 。
位域访问是根据底层types的操作来实现的。 在这个例子中, unsigned int
。 所以如果你有这样的东西:
struct x { unsigned int a : 4; unsigned int b : 8; unsigned int c : 4; };
当你访问字段b
,编译器访问一个完整的unsigned int
,然后移动并屏蔽适当的位范围。 (好吧,这不是必须的 ,但是我们可以假装它是的。)
在大端,布局将是这样的(最重要的第一位):
AAAABBBB BBBBCCCC
在小端,布局将是这样的:
BBBBAAAA CCCCBBBB
如果你想从小端访问大端排列,反之亦然,你必须做一些额外的工作。 这种可移植性的提高有一个性能损失,由于结构布局已经不可移植,语言实现者使用更快的版本。
这做了很多假设。 在大多数平台上还要注意sizeof(struct x) == 4
。
位字段将以不同的顺序存储,具体取决于机器的字节顺序,在某些情况下这可能无关紧要,但在其他情况下可能很重要。 比方说,你的ParsedInt结构代表通过networking发送的数据包中的标志,一个小端机器和一个大端机器以不同于发送字节的顺序读取这些标志,这显然是一个问题。