C / C ++:强制位字段顺序和alignment
我读过结构中位字段的顺序是特定于平台的。 如果我使用不同的编译器特定的打包选项,那么这个保证数据是否按照正确的顺序存储呢? 例如:
struct Message { unsigned int version : 3; unsigned int type : 1; unsigned int id : 5; unsigned int data : 6; } __attribute__ ((__packed__));
在具有GCC编译器的英特尔处理器上,字段按照显示的内容放在内存中。 Message.version是缓冲区中的前3位,并且接着是Message.type。 如果我为各种编译器find等效的结构打包选项,这是跨平台的吗?
不,它不会是完全便携的。 结构的打包选项是扩展,本身不是完全可移植的。 除此之外,C99§6.7.2.1第10段说:“一个单元内的位字段的分配顺序(从高到低或从低到高)是由实现定义的。
例如,即使是单个编译器也可能根据目标平台的字节顺序将字段放在不同的位置。
从编译器到编译器,位字段差别很大,对不起。
用GCC,大端机先排大端,小端机先排小端。
K&R说:“结构的相邻[位]字段成员按实现相关的方向打包到实现相关的存储单元中。当另一个字段之后的字段不适合时…它可以在单元之间分割,或者单元可以是填充宽度0的未命名字段强制此填充…“
因此,如果你需要机器独立的二进制布局,你必须自己做。
这最后一条语句也适用于由于填充引起的非位字段 – 但是,所有编译器似乎都有某种强制结构字节打包的方式,因为我看到您已经发现了GCC。
位字段应该避免 – 即使在相同的平台上,它们在编译器之间也不是很便携。 从C99标准6.7.2.1/10 – “结构和联合说明符”(在C90标准中有类似的措词):
一个实现可以分配任何足够大的可寻址存储单元来容纳一个位域。 如果有足够的空间,紧接在结构中的另一个比特字段之后的比特字段应被打包到相同单元的相邻比特中。 如果剩余空间不足,则将不合适的比特字段放入下一个单元,或与相邻单元重叠是实现定义的。 一个单元内的位域分配顺序(从高到低或从低到高)是实现定义的。 未指定可寻址存储单元的alignment方式。
你不能保证位字段是否会跨越一个int边界,你不能确定一个位字段是从int的低端开始还是从int的高端开始(这与处理器是无关的大端或小端)。
首选位掩码。 使用内联(甚至是macros)来设置,清除和testing这些位。
字节顺序是指字节顺序而不是位顺序。 现在 ,99%的订单是固定的。 但是,当使用位域时,应该在count中进行字节顺序排列。 看下面的例子。
#include <stdio.h> typedef struct tagT{ int a:4; int b:4; int c:8; int d:16; }T; int main() { char data[]={0x12,0x34,0x56,0x78}; T *t = (T*)data; printf("a =0x%x\n" ,t->a); printf("b =0x%x\n" ,t->b); printf("c =0x%x\n" ,t->c); printf("d =0x%x\n" ,t->d); return 0; } //- big endian : mips24k-linux-gcc (GCC) 4.2.3 - big endian a =0x1 b =0x2 c =0x34 d =0x5678 // - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2 a =0x2 b =0x1 c =0x34 d =0x7856
大多数情况下,大概是这样,但是不要把农场押在上面,因为如果你错了,你会失去大的。
如果确实需要具有相同的二进制信息,则需要使用位掩码创build位域,例如,对消息使用无符号的短整型(16位),然后使versionMask = 0xE000表示三个最高位。
结构中的alignment也有类似的问题。 例如,Sparc,PowerPC和680×0 CPU都是big-endian,Sparc和PowerPC编译器的默认默认值是在4字节的边界上alignmentstruct成员。 但是,我用于680×0的一个编译器仅在2字节的边界上alignment,而且没有选项可以更改alignment方式!
所以对于一些结构来说,Sparc和PowerPC上的大小是相同的,但在680×0上更小,并且一些成员在结构内部具有不同的内存偏移量。
这是我工作的一个项目的一个问题,因为在Sparc上运行的服务器进程将查询客户端,并发现它是高端的,并假设它可以在networking上喷出二进制结构,客户端可以应付。 这在PowerPC客户端上运行良好,并且在680×0客户端上崩溃。 我没有写代码,花了相当长的时间才发现问题。 但是一旦我确定就很容易解决。
当然,最好的答案是使用读/写位stream的类作为stream。 使用C位字段结构只是不能保证。 更不用说在现实世界编码中被认为是不专业/懒惰/愚蠢的。