现在的位置: 首页 > 综合 > 正文

关于位域的字节内存储顺序、字节对齐、字节序以及符号

2018年04月16日 ⁄ 综合 ⁄ 共 2335字 ⁄ 字号 评论关闭

        位域,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.带位域的结构内存中各个位域的存储方式取决于具体的编译程序:可以从左向右,也可
  一从右向左存储。

【上篇】
【下篇】

抱歉!评论已关闭.