有关位域的理解和说明
关于字节序:是字节之间的事情,多字节数据类型,字节的排序问题,类似于在十进制数中,十位放在个位的左边还是右边的问题。例如int类型有4个字节,值为0x12345678,地址为0x000010~0x000013,那么在0x000010是存放0x12还是存放0x78.分下面两种情况:
1)低地址放低位(小端字节序,小端即低位在前)。实际存储为0x78 56 34 12. 使用这种字节序的典型为x86机器,即intel和amd的cpu。
2)低地址放高位(大端字节序)。实际存储为0x12 34 56 78. 其余的厂家一般使用这种字节序。sun,ibm等等。另外网络字节序也是这种,二进制文件如音视频文件中一般也为大端。
在字节内部,一个字节的二进制排序,不存在大小端问题。就和平常书写的一样,先写高位,即低地址存储高位。如char a=0x12.存储从低位到高位就为0001 0010。
如:
struct XX
{
unsigned int i:3;
unsigned int j:4;
};
XX.i = 1;
XX.j = 3;
(gdb) p xx.i
$1 = 1
(gdb) p xx.j
$2 = 3
(gdb) x/tw &xx
0xbfaa3024: 01000000 00000001 01011100 1 0011 001
例子中,i占据最低三bit,j占据次低四bit。
{
unsigned int i:3;
unsigned int j:4;
};
XX.i = 1;
XX.j = 3;
(gdb) p xx.i
$1 = 1
(gdb) p xx.j
$2 = 3
(gdb) x/tw &xx
0xbfaa3024: 01000000 00000001 01011100 1 0011 001
例子中,i占据最低三bit,j占据次低四bit。
字节内部二进制排序的特殊情况,字节被位域分割的话,不同位域会根据cpu的不同采用不同排序(大端或小端)。或者说一个字节内两个或多个位域的排练顺序:从左至右还是从右至左。
例如,在流媒体协议rtp的头部格式为:版本(2bit),填充(1bit),扩展(1bit),csrc计数(4bit),标志(1bit),负载类型(7bit),序列号(16bit),时间戳(32bit)。在intel的机器中,定义头部类型时,应该如下:(intel为小端字节序,而网络流为大端字节序,相反)
- typedef struct
- {
- /**//* byte 0 此处,注意顺序与规定相反,在存储的时候,这个字节内部由低位到高位为version,padding,extension,csrc_len*/
- uint8_t csrc_len:4;
- uint8_t extension:1;
- uint8_t padding:1;
- uint8_t version:2;
- /**//* byte 1 */
- uint8_t payload:7;
- uint8_t marker:1;
- /**//* bytes 2, 3 */
- uint16_t seq_no;
- /**//* bytes 4-7 */
- uint32_t timestamp;
- /**//* bytes 8-11 */
- uint32_t ssrc; /**//* stream number is used here. */
- } RTP_FIXED_HEADER;
另外,上图,位域的顺序的说明,只在一个字节内有效。如果跨越字节,则按照声明的顺序分配。
例2,程序如下:
- #include <stdio.h>
- int main()
- {
- struct bitfield {
- int ia:2;
- int ib:6;
- } field;
- field.ia=1;
- field.ib=4;
- char * c;
- c=(char *)&field;
- printf("%d\n",*c);
- getchar();
- return 1;
- }
在X86上运行结果是17(000100 01),而不是68(01 000100)
而当使用union进行类型间转换时,情况又有所不同,请看下面的程序:
1 #include <stdio.h> 2 int main() 3 { 4 struct foo4 { 5 char a : 2; 6 char b : 3; 7 char c : 3; 8 char d : 2; 9 char e : 6; 10 }; 11 union foo5 12 { 13 short s; 14 foo4 ut; 15 }m; 16 struct foo4 t; 17 t.a = 1; 18 t.b = 2; 19 t.c = 3; 20 t.d = 2; 21 t.e = 0; 22 23 m.ut = t; 24 25 //short s = (short)t; 26 printf("%d\n" , m.s); 27 28 29 int len = sizeof(foo4); 30 printf("%d",len); 31 return 0; 32 }
此程序输出m.s = 617(1001101001),说明进制转换之后,依然是先声明的变量在低位,后生命的变量在高位。
但此时如果将程序中的第21行注释掉,我的环境则输出-12951,这里的初始化疏忽,也迷惑了我很久才找到,初始化的部分是对的,但是没初始化的位域部分出现随机值。请大家在写程序的时候一定要注意。
其他的注意事项:
1.位域的长度不能大于int对象所占用的位数。
2.由于位域的实现会因编译程序的不同而不同,因此使用位域会影响程序的可移植性,在
不是非要使用时最好不要使用.
3.尽管位域可以节省空间,却增加了处理时间。
4.位域的位置不能访问,因此不能对位域使用地址运算符&,也不能使用位域的数组。
5.带位域的结构内存中各个位域的存储方式取决于具体的编译程序:可以从左向右,也可
一从右向左存储。
6.通常理解的位域变量的宽度不能超过字节,但是也有特殊的情况:如果设置空白位(无变量名,仅作为占位用)则是可以跨越字节的。