我有这个C结构:(代表一个IP数据报)
struct ip_dgram { unsigned int ver : 4; unsigned int hlen : 4; unsigned int stype : 8; unsigned int tlen : 16; unsigned int fid : 16; unsigned int flags : 3; unsigned int foff : 13; unsigned int ttl : 8; unsigned int pcol : 8; unsigned int chksm : 16; unsigned int src : 32; unsigned int des : 32; unsigned char opt[40]; };
我正在给它赋值,然后用16位字打印它的内存布局,如下所示:
//prints 16 bits at a time void print_dgram(struct ip_dgram dgram) { unsigned short int* ptr = (unsigned short int*)&dgram; int i,j; //print only 10 words for(i=0 ; i<10 ; i++) { for(j=15 ; j>=0 ; j--) { if( (*ptr) & (1<<j) ) printf("1"); else printf("0"); if(j%8==0)printf(" "); } ptr++; printf("\n"); } } int main() { struct ip_dgram dgram; dgram.ver = 4; dgram.hlen = 5; dgram.stype = 0; dgram.tlen = 28; dgram.fid = 1; dgram.flags = 0; dgram.foff = 0; dgram.ttl = 4; dgram.pcol = 17; dgram.chksm = 0; dgram.src = (unsigned int)htonl(inet_addr("10.12.14.5")); dgram.des = (unsigned int)htonl(inet_addr("12.6.7.9")); print_dgram(dgram); return 0; }
我得到这个输出:
00000000 01010100 00000000 00011100 00000000 00000001 00000000 00000000 00010001 00000100 00000000 00000000 00001110 00000101 00001010 00001100 00000111 00001001 00001100 00000110
但我期待这一点:
输出是部分正确的; 在某处,字节和半字节似乎互换。 这里有一些sorting问题吗? 位域不适合这个目的吗? 我真的不知道。 任何帮助? 提前致谢!
不,位域不适合这个目的。 布局是依赖于编译器的。
对于要控制结果布局的数据,使用位域通常不是一个好主意,除非您有(编译器特定的)方法(如#pragma
s)来执行此操作。
最好的方法可能是实现这个没有位域,即通过自己做所需的按位操作。 这是烦人的,但方式比以某种方式来解决这个问题更容易。 另外,它是平台无关的。
将标题定义为一个16位字的数组,然后可以很容易地计算出校验和。
虽然问题很久以前就被问过了,但是对结果的解释却没有答案。 我会回答,希望对某人有用。
我将用数据结构的前16位来说明这个错误。
请注意:只有使用你的处理器和编译器才能保证这个解释是正确的。 如果有这些变化,行为可能会改变。
领域:
unsigned int ver : 4; unsigned int hlen : 4; unsigned int stype : 8;
分配给:
dgram.ver = 4; dgram.hlen = 5; dgram.stype = 0;
编译器开始分配从偏移量0开始的位字段。这意味着数据结构的第一个字节存储在内存中,如下所示:
Bit offset: 7 4 0 ------------- | 5 | 4 | -------------
分配后的前16位看起来像这样:
Bit offset: 15 12 8 4 0 ------------------------- | 5 | 4 | 0 | 0 | ------------------------- Memory Address: 100 101
您正在使用无符号16位指针取消引用内存地址100.结果地址100被视为16位数的LSB。 101被视为16位数的MSB。
如果你在十六进制打印* ptr你会看到这个:
*ptr = 0x0054
你的循环运行在这个16位值,因此你得到:
00000000 0101 0100 -------- ---- ---- 0 5 4
解决方案:更改元素的顺序
unsigned int hlen : 4; unsigned int ver : 4; unsigned int stype : 8;
并使用unsigned char *指针来遍历和打印值。 它应该工作。
请注意,正如别人所说,这种行为是平台和编译器的具体情况。 如果有任何这些更改,您需要验证您的数据结构的内存布局是否正确。
C11标准说:
一个实现可以分配任何足够大的可寻址存储单元来容纳一个位域。 如果剩余足够的空间,紧接在结构中的另一个比特字段之后的比特字段应被打包到相同单元的相邻比特中。 如果剩余空间不足,则将不合适的比特字段放入下一个单元,或与相邻单元重叠是实现定义的。 一个单元内的位域分配顺序(从高到低或从低到高)是实现定义的。
我敢肯定,这是不可取的,因为这意味着你的领域之间可能会有填充,你不能控制你的领域的顺序。 不仅如此,在网络字节顺序方面,您正在执行这个任务。 此外,想象一下,如果一个unsigned int
只有16位,并且要求将一个32位的位域放进去:
指定位字段宽度的表达式应该是一个整数常量表达式,其中一个非负值不会超过指定类型的对象的宽度,如果冒号和表达式被忽略,该表达式将被指定。
我建议使用一个unsigned char
数组而不是一个结构。 这样可以保证对填充和网络字节顺序的控制。 从你想要的结构的大小开始。 我假设你声明这个常量,如IP_PACKET_BITCOUNT: typedef unsigned char ip_packet[(IP_PACKET_BITCOUNT / CHAR_BIT) + (IP_PACKET_BITCOUNT % CHAR_BIT > 0)];
写一个函数, void set_bits(ip_packet p, size_t bitfield_offset, size_t bitfield_width, unsigned char *value) { ... }
它允许你设置从位p[bitfield_offset / CHAR_BIT]
位bitfield_offset % CHARBIT
值,高达bitfield_width
位的长度。 这将是你的任务中最复杂的部分。
然后你可以为VER_OFFSET 0和VER_WIDTH 4,HLEN_OFFSET 4和HLEN_WIDTH 4等定义标识符,以使修改数组看起来不那么轻松。
编译器是否依赖,取决于你是想编写一个非常快速的程序,或者你想要一个与不同编译器一起工作的程序。 要为C写一个快速,紧凑的应用程序,请使用位域为/的Studen。 如果你想要一个缓慢的通用程序,长的代码。
我同意放松的说法。 位字段是依赖于编译器的。
如果你需要按照特定的顺序,将数据打包到一个字符数组的指针中。 增加被包装的元素的大小。 打包下一个元素。
pack( char** buffer ) { if ( buffer & *buffer ) { //pack ver //assign first 4 bits to 4. *((UInt4*) *buffer ) = 4; *buffer += sizeof(UInt4); //assign next 4 bits to 5 *((UInt4*) *buffer ) = 5; *buffer += sizeof(UInt4); ... continue packing } }