1.一个fans写的协议相关的代码,bit域的值解析和想象中的不一样。问题结构头:解析代码及测试结果:也就是说函数hexdump()解析出来的内存十六进制为8183203B...从数据框解析出来的opcode=0x8是我的粉丝不明白为什么解析出来的值是0x8。这个问题其实是位域问题和字节序问题。测试代码不再废话,直接写一段测试代码#include//简化结构structiphdr{unsignedcharfin:1;unsignedcharrsv:3;unsignedcharopcode:4;unsignedcharmask:1;unsignedcharpayload:7;unsignedchara;unsignedcharb;};main(){structiphdrt;unsignedchar*s;//清除内存,防止乱码memset(&t,0,4);//使用指向结构体变量的指针ts=(unsignedchar*)&t;//pass数组的访问方式修改了内存的值,因为hexdump解析出来的值是0x8183,//所以0x81一定是内存数据的最低字节s[0]=0x81;s[1]=0x83;//打印出来位域成员printf("fin:%drsv:%dopcode:%dmask:%dpaylod:%d\n",t.fin,t.rsv,t.opcode,t.mask,t.payload)的值;}执行结果:fin:1,rsv:0,opcode:8,mask:1paylod:65分析:如下图,紫色部分为位域成员对应的内存中的实际空间布局,以及地址从左到右增加第一个字节分配后0x81的值,各字段对应的二进制:fin:1rsv:0opcode:1000mask:1paylod:1000001如上图所示,内存第一个字节为0x81,第二个字节为0x83;第一个字节是0x81,最低bit[0]对应fin,bit[3:1]对应rsv,bit[7:4]对应opcode;第二个字节0x83的最低bit[0]对应mask,bit[7:1]对应payload。所以结果是显而易见的。2、什么是位域?有些信息在存储时,不需要占用一个完整的字节,只需要占用几个或一个二进制位即可。例如存储开关量时,只有0和1两种状态,可以用二进制位。为了节省存储空间和使处理更容易,C语言提供了一种称为“位域”或“位段”的数据结构。所谓“位域”就是把一个字节中的二进制分成几个不同的区域,并说明每个区域的位数。每个域都有一个域名,允许在程序中通过域名进行操作。这样,一个字节的二进制位域就可以表示几个不同的对象。1、位域定义及位域变量说明位域定义与结构体定义类似,其形式为:structbitfield结构名{bitfieldlist};位域列表形式为:类型说明符位域名:bit域长度如爱好者给出的例子:structiphdr{unsignedcharfin:1;unsignedcharrsv:3;unsignedcharopcode:4;unsignedcharmask:1;unsignedcharpayload:7;unsignedchara;无符号字符;};位域变量的描述与结构变量相同。有先定义后解释、同时定义解释或直接解释三种方式。例如:structbs{inta:8;intb:2;intc:6;}data;表示data是一个bs变量,共占用两个字节。其中位域a占8位,位域b占2位,位域c占6位。位域的定义有如下说明:一个位域必须存放在同一个字节中,不能跨越两个字节。如果一个字节的剩余空间不足以存储另一个位域,则应从下一个单元开始存储该位域。也可以有意地使位字段从下一个单元开始。例如:structbs{unsigneda:4unsigned:0/space/unsignedb:4/storagefromnextunit/unsignedc:4};在这个位域定义中,a占第一个字节的4位,后4位填充0表示未使用,b从第二个字节开始占4位,c占4位。位域可以没有位域,在这种情况下它仅用于填充或位置调整。不能使用未命名的位域。例如:structk{inta:1int:2/这2位不能使用/intb:3intc:2};从上面的分析可以看出,位域本质上是一种结构类型,只是它的成员是按二进制分配的。这是位域操作的表达方式,即在后面加“:1”表示该成员的大小占定义类型的1位,“:2”占2位,以此类推。当然大小不能超过定义类型中包含的总位数。一个字节(byte)是8位(binarybits)。比如你结构体中定义的类型是u_char,一个字节,一共8位,最大不能超过8。在32位机器下,short是2个字节,一共16位,最大不能超过16个,int是4个Bytes,一共32bit,最大不能超过32个。以此类推。位字段定义节省空间。比如你上面的结构体中,定义的变量类型是u_char,是单字节类型,即8bit。fc_subtype占4位,fc_type占2位,fc_protocol_version占2位,一共8位,正好一个字节。其他8个成员各占1位,共8位,正好是一个字节。因此,如果你的结构的大小是用sizeof(structframe_control)计算的,它就是2bytes。3、如何判断是bigendian还是littleendian?计算机硬件有两种存储数据的方式:大端和小端。大端:高位字节在前,低位字节在后。这就是人类读取和写入值的方式。Little-endian:低位字节在前,高位字节在后。0x1234567的big-endian和little-endian写在下图中。为什么会有小端字节序?答案是计算机电路先处理低位字节,这样效率更高,因为计算是从低位字节开始的。因此,计算机的内部处理是小端的。然而,人类仍然习惯于以大端顺序读写。因此,除了计算机内部处理,其他场合几乎都是big-endian,比如网络传输、文件存储等。计算机在处理字节顺序时,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,首先是第一个字节,然后是第二个字节。如果是big-endian,则先读高位字节,后读低位字节。小字节序正好相反。理解这一点以理解计算机如何处理字节顺序。当处理器读取外部数据时,它必须知道数据的字节顺序并将其转换成正确的值。然后,正常使用这个值,完全不管字节顺序。即使是向外部设备写入数据,也不需要考虑字节顺序,正常写入一个值即可。外部设备将自行处理字节顺序。该示例仍然使用上面的示例,但进行以下修改#includestructiphdr{unsignedcharfin:1;unsignedcharrsv:3;unsignedcharopcode:4;unsignedcharmask:1;unsignedcharpayload:7;};main(){structiphdrt;unsignedshort*s;memset(&t,0,2);s=(unsignedchar*)&t;//注意,直接赋值0x8183,因为常量必须和host的字节序一致,//小端:83为低字节,//bigendian:81为低字节*s=0x8183;printf("fin:%drsv:%dopcode:%dmask:%dpaylod:%d\n",t.fin,t.rsv,t.opcode,t.mask,t.payload);}执行结果:fin:1rsv:1opcode:8mask:1paylod:64从结果我们可以看出0x8183的接收值有对应的二进制关系:fin:1rsv:001opcode:1000mask:1paylod:1000000如上图所示,第一个字节内存为0x83,第二个字节为0x81【与前面的例子不同,因为我们直接赋值0x8183,而常量是小端序,所以低字段为0x83】;可以看出低字节83给了fin+rsv+opcode。所以,这说明口君的ubuntu是little-endianbyteorder。4.扩展例子,继续修改结构如下。当位域成员的组合大小不足以容纳一个完整的字节时,请验证每个成员在内存中的布局。#includestructiphdr{unsignedcharfin:1;unsignedcharopcode:4;unsignedchara;unsignedcharb;};main(){structiphdrt;unsignedchar*s;memset(&t,0,2);s=(unsignedshort*)&t;t.fin=1;t.opcode=0xf;printf("%x\n",s[0]);}fin:1opcode:1111在内存中的形式如下:如果将fin的值改为0:t.fin=0;执行结果如下:fin:0opcode:1111在内存中的形式如下:5、遇到类似问题,一定要写一些例子来验证。对于初学者,建议参考上面的例子。