位域,Bit-field,又称位段。位域操作是在位操作之外的另一种操作比特位的方法。
相对于按位操作而言,操作位域可以“像”操作普通的变量一样。所以在需要进行比特位
操作的场合,硬件控制、协议处理, 位域被广泛应用。位域可以定义在class、struct、
union中,作为他们的数据成员。
使用位域的好处,主要是不需要进 行与或非以及相关掩码的处理。但是,福兮祸之
所掩。使用位段封装了与或非以及掩码的操作,也掩盖了位操作的实质,让人很容易忘
记位操作需要注意的问题。在位域操作上,有几个容易被忽略的问题:字节内顺序、字
节对齐、字节间顺序、符号特性。
所谓字节内顺序,是说一个字节内(或一个用来定义位域的变量内)两个或多个位域
的排练顺序:从左至右还是从右至左。如果你要定义位域用来控制CPU的第20根地址线上
的信号,但是你不知道或者不考虑这个左右问题,那么很可能你的代码就会犯错误。位域
的字节内存储顺序是编译器实现相关的,所以要弄明白这个左右问题,必须先在目标编译
系统上进行测试。 在Intel PM centrino + Suse Linux 10 + GCC 4.0 上的测试结果是:
按照定义先后顺序,从低位往高位存储。
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。
所谓字节对齐,是指编译器在处理struct、class、union的存储位置时,按照设置的
或者默认的最小字节数进行截断、填充。最少分配的单元是用来定义位域的类型的字节大
小。如果定义一个位域:
unsigned int a:5;
编译器会分配一个4字节大小的存储空间来存储这个位域。这个例子是填充。
unsigned int a:5;
unsigned int b;
unsigned int c:13;
这个例子中,编译器会分配4+4+4个字节,因为按照定义顺序来处理存储位置,而不是先
统一处理位域。
unsigned char a:4;
unsigned char b:6;
unsigned char c:3;
这个例子中,编译器会分配1+1+1个字节。因为4+6>8 (sizeof(unsigned char)),a,b各自
分配了一个字节。然后6+3>8,又为c分配了一个字节。
另外,普通数据成员的字节对齐问题,对于位域,一样适用。
unsigned char a:4;
unsigned char b:6;
unsigned char c:3;
unsigned int k;
这个例子中,编译器会分配8个字节。 因为这种定义下是4字节对齐,前面三个字节后面
的一个字节被填充。
字节间顺序,是说网络字节序和主机字节序。当编写的是网络通信的程序时,要注意
网络字节序和主机字节序的问题。尤其当定义了多个位域或者单个位域的长度超过八bit的
时候。在应用程序中,使用主机字节序;在网络中传输时使用网络字节序。这个很明确。
字节序的转换是在穿透协议栈的过程中,解析协议头部的时候——payload永远由用户自己
定义,爱什么顺序是什么顺序。如果定义了位域用来解析协议头部,那么就要注意你读写
的时候是什么字节序,应该在转换字节序之前还是之后来读写位域了。
位域的符号特性,是说位域变量的正或者负的问题。当使用有符号类型来定义位域,
并且使用到了正负(有意或者无意)特性作为判断条件时,就有问题了。
#include
using namespace std;
class NT
{
public:
NT(int i1,int j1,int k1)
{
i = i1;
j = j1;
k = k1;
m &= 0;
};
int i:1;
int j:2;
int k:13;
int m:16;
};
int main()
{
NT nt((int)1, (int)2, (int)3);
cout << nt.i << " " < return 0;
}
上面这个程序的输出是-1 -2 3. 和我们预想的1,2,3不同。有符号数在机器中是以
补码的形式存在的,其正负的判断有其规则。位域是以原码的形式来进行操作的,这中
间有差异,造成了上面的结果。而关于位域的正负数判断,也不是简单的首bit的0或1来
决定,否则上面的结果就应该是-1 -2 -3或者1 2 3了。位域的实现,是编译器相关的。
建议是,使用位域不要使用正负这样的特性——理论上来说,应该只关注定义的那几个
bit的0或者1,是无符号的。当然,像上面那条打印也没有使用正负特性。这就是无意识
的过程中使用了正负特性。可以使用无符号类型来定义位域,这样不会产生正负号这样的
问题。
其他的注意事项:
1.位域的长度不能大于int对象所占用的位数。
2.由于位域的实现会因编译程序的不同而不同,因此使用位域会影响程序的可移植性,在
不是非要使用时最好不要使用.
3.尽管位域可以节省空间,却增加了处理时间。
4.位域的位置不能访问,因此不能对位域使用地址运算符&,也不能使用位域的数组。
5.带位域的结构内存中各个位域的存储方式取决于具体的编译程序:可以从左向右,也可
一从右向左存储。
相对于按位操作而言,操作位域可以“像”操作普通的变量一样。所以在需要进行比特位
操作的场合,硬件控制、协议处理, 位域被广泛应用。位域可以定义在class、struct、
union中,作为他们的数据成员。
使用位域的好处,主要是不需要进 行与或非以及相关掩码的处理。但是,福兮祸之
所掩。使用位段封装了与或非以及掩码的操作,也掩盖了位操作的实质,让人很容易忘
记位操作需要注意的问题。在位域操作上,有几个容易被忽略的问题:字节内顺序、字
节对齐、字节间顺序、符号特性。
所谓字节内顺序,是说一个字节内(或一个用来定义位域的变量内)两个或多个位域
的排练顺序:从左至右还是从右至左。如果你要定义位域用来控制CPU的第20根地址线上
的信号,但是你不知道或者不考虑这个左右问题,那么很可能你的代码就会犯错误。位域
的字节内存储顺序是编译器实现相关的,所以要弄明白这个左右问题,必须先在目标编译
系统上进行测试。 在Intel PM centrino + Suse Linux 10 + GCC 4.0 上的测试结果是:
按照定义先后顺序,从低位往高位存储。
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。
所谓字节对齐,是指编译器在处理struct、class、union的存储位置时,按照设置的
或者默认的最小字节数进行截断、填充。最少分配的单元是用来定义位域的类型的字节大
小。如果定义一个位域:
unsigned int a:5;
编译器会分配一个4字节大小的存储空间来存储这个位域。这个例子是填充。
unsigned int a:5;
unsigned int b;
unsigned int c:13;
这个例子中,编译器会分配4+4+4个字节,因为按照定义顺序来处理存储位置,而不是先
统一处理位域。
unsigned char a:4;
unsigned char b:6;
unsigned char c:3;
这个例子中,编译器会分配1+1+1个字节。因为4+6>8 (sizeof(unsigned char)),a,b各自
分配了一个字节。然后6+3>8,又为c分配了一个字节。
另外,普通数据成员的字节对齐问题,对于位域,一样适用。
unsigned char a:4;
unsigned char b:6;
unsigned char c:3;
unsigned int k;
这个例子中,编译器会分配8个字节。 因为这种定义下是4字节对齐,前面三个字节后面
的一个字节被填充。
字节间顺序,是说网络字节序和主机字节序。当编写的是网络通信的程序时,要注意
网络字节序和主机字节序的问题。尤其当定义了多个位域或者单个位域的长度超过八bit的
时候。在应用程序中,使用主机字节序;在网络中传输时使用网络字节序。这个很明确。
字节序的转换是在穿透协议栈的过程中,解析协议头部的时候——payload永远由用户自己
定义,爱什么顺序是什么顺序。如果定义了位域用来解析协议头部,那么就要注意你读写
的时候是什么字节序,应该在转换字节序之前还是之后来读写位域了。
位域的符号特性,是说位域变量的正或者负的问题。当使用有符号类型来定义位域,
并且使用到了正负(有意或者无意)特性作为判断条件时,就有问题了。
#include
using namespace std;
class NT
{
public:
NT(int i1,int j1,int k1)
{
i = i1;
j = j1;
k = k1;
m &= 0;
};
int i:1;
int j:2;
int k:13;
int m:16;
};
int main()
{
NT nt((int)1, (int)2, (int)3);
cout << nt.i << " " < return 0;
}
上面这个程序的输出是-1 -2 3. 和我们预想的1,2,3不同。有符号数在机器中是以
补码的形式存在的,其正负的判断有其规则。位域是以原码的形式来进行操作的,这中
间有差异,造成了上面的结果。而关于位域的正负数判断,也不是简单的首bit的0或1来
决定,否则上面的结果就应该是-1 -2 -3或者1 2 3了。位域的实现,是编译器相关的。
建议是,使用位域不要使用正负这样的特性——理论上来说,应该只关注定义的那几个
bit的0或者1,是无符号的。当然,像上面那条打印也没有使用正负特性。这就是无意识
的过程中使用了正负特性。可以使用无符号类型来定义位域,这样不会产生正负号这样的
问题。
其他的注意事项:
1.位域的长度不能大于int对象所占用的位数。
2.由于位域的实现会因编译程序的不同而不同,因此使用位域会影响程序的可移植性,在
不是非要使用时最好不要使用.
3.尽管位域可以节省空间,却增加了处理时间。
4.位域的位置不能访问,因此不能对位域使用地址运算符&,也不能使用位域的数组。
5.带位域的结构内存中各个位域的存储方式取决于具体的编译程序:可以从左向右,也可
一从右向左存储。