C中的位域

C中的位域
    在求职笔试中,C中的位域是一个常考点,特别是在嵌入式软件中更常见。位域的最大好处是可以根据自己需要定制位数,从而节省空间,例如:嵌入式编程稀缺的内存资源。还有在网络通讯中,对头信息部分的结构定义也常用到位域,少传一位是一位啊。
    这里来分析EMC的一道笔试题(07年招聘试题):
1 typedef struct bitstruct 2 { 3 int b1:5; 4 int :2; 5 int b2:2; 6 }bitstruct; 7 int main(int argc, char *argv[]) 8 { 9 bitstruct b;10 printf("%d\n",sizeof(bitstruct));11 memcpy(&b,"EMC Examination",sizeof(b));12 printf("%d,%d\n",b.b1,b.b2);13 return 0;14 }
请问在little-endian systems(系统默认的存放顺序)中,输出结果是什么?
答案是
4  
5 - 2

所需知识点:
1. 位域 的概念和特点
    C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”(bit field)。(1)位段成员的类型必须指定为unsigned或int类型;(2)若某一位段要从另一个字开始存放,用:0长度为0的空位段,作用就是使下一个位段从下一个存储单位(视不同编译系统而异)开始存放;(3)一个位段必须存储在同一存储单元中,不能跨两个单元;(4)可以定义无名字段例如":2";(5)位段的长度不能大于存储单元的长度,也不能定义位段数组;(6)位段可以用整形格式符输出;(7)位段可以在数值表达式中引用,它会被系统自动地转换成整形数。[1][3]

2. Little-endian  systems的内存布局特点
    
    先问一个问题:Endian这个词是什么意思?

    “endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

    我们一般将endian翻译成“ 字节序”,将big endian和little endian称作“大尾”和“小尾”。这也在当今的CPU派别中一样存在。Motorola的PowerPC系列CPU采用的Big-endian, Intel的X86系列CPU采用的是Little-endian。Little-endian的特点是高高低低,即高位地址存放最高有效字节,低位地址存放最低有效字节;而Big-endian正好相反。下面用图的方式说明起来更直观,例如0x12345678( 特别注意,这是单个数,不是字符串,如果是字符串就不一定这样了。之前没有特别注意这点,害的我多花了冤枉时间)。
Big Endian

   低地址                                            高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     12     |      34    |     56      |     78    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

   低地址                                            高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     78     |      56    |     34      |     12    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    上面的字节序的不同,在单机的操作中没有问题,因为一台单机就是采用单一的字节序嘛!但是在两个不同的主机进行协作时,就会出现问题了;另外在网络通讯中也同样会出现问题,详细参考[2]

就单个字节而言,也会有这样的问题:比特序有差别吗?

    也分成两种序,如果我们处理的基本单位是字节以上的话,对此就不必担心了,因为CPU存储操作的最小单元是字节,所以比特位的顺序对我们来就是透明的,我们在读取某个字节时,不管它用的是Big endian 还是Little endian,我们读到的都是一个同样的字节,只不过硬件在读写时的顺序,一个是从高到底另一个是从低到高,对我们的使用不产生影响。但是如果涉及到位域的存放问题,还是要特别小心,上面这道题就是一个非常的好的例子。

Big Endian

   msb                                                         lsb
   ---------------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |   1  |   0  |   1  |   1  |   0  |   1  |   0  |   0  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

   lsb                                                         msb
   ---------------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |   0  |   0  |   1  |   0  |   1  |   1  |   0  |   1  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


3. memcpy() 与strcpy()的区别
    这个问题很简单,可以想象成这样:memcpy的基本单位是位,strcpy的基本单位是字符。所以,memcpy会根据提供的长度完全按位拷贝,而strcpy是两个字符串之间的拷贝。

     回来原来的问题上,在采用little endian的BUS64中,struct bitstruct在没涉及到高低位问题时,也就是我们平时常会画出的一种形式是:
{b1 b1 b1 b1 b1 Ø Ø b2   b2 Ø Ø Ø Ø Ø Ø Ø    ØØØØØØØØ   ØØØØØØØØ};
在内存中的实际布局是:

   低地址                                                 高地址
   -------------------------
------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+-+-+-+-+-+-+-+-+
   | b2 Ø Ø b1 b1 b1 b1 b1 |Ø Ø Ø Ø Ø Ø Ø b2 |ØØØØØØØØ |ØØØØØØØØ |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+-+-+-+-+-+-+-+-+

Ø表示空填充,这当中包含一个原则:一个位域的存放不可以跨字存放,但可以跨字节存储;这里采用的比特续也是little endian,说明了比特序对操作也是有影响的嘛!

    memcpy按位拷贝“EMC Examination”到b中,一个字符是8位; 又因为sizeof(b)=4,所以只写入"EMC "。"EMC "对应的位序列是:{0100 0101  0100 1101  0100 0011  0010 0000},从这里写到内存里的形式是:(在这里不要被little endian给迷惑了,E不是放在高地址,参看上面 红色的特别注意

    低地址                                                 高地址
   -------------------------
-------------------- ------------ ----->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+-+-+-+-+-+-+ -+-+
    |  b2  Ø Ø  b1 b1  b1 b1 b1  |Ø Ø Ø Ø Ø Ø Ø  b2  |ØØØØØØØØ  |ØØØØØØØØ  |
   | 0  1 0 0  0  1 0  1  |0  1  0  0 1  1 0  1  |0100 0011      |0010 0000     |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -+-+-+-+-+-+-+-+- +

printf("%d,%d\n",b.b1,b.b2);读出来的b1是00101,它的值是5; b2是10,转换成十进制是-2。


到了这一步,似乎题目已经解答出来了,但还有两个地方有疑惑:
1.struct bitstruct在内存中的位存放顺序是这样的么?
   低地址                                                 高地址
   -------------------------
-------------------- ------------ ----->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+-+-+-+-+-+-+ -+-+
    b2  Ø Ø  b1 b1  b1 b1 b1  |Ø Ø Ø Ø Ø Ø Ø  b2  |ØØØØØØØØ  |ØØØØØØØØ  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -+-+-+-+-+-+-+-+- +

2.memcpy函数在按位拷贝时的拷贝顺序是这样的么?
    低地址                                                 高地址
   -------------------------
-------------------- ------------ ----->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+-+-+-+-+-+-+ -+-+
    | 01000101  | 01001101  |0100 0011  |0010 0000  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -+-+-+-+-+-+-+-+- +
关于上面两个问题的验证工作留给路过的朋友来做:-P .....  欢迎批评指正

[1]谭浩强 C程序设计 第二版 清华大学出版社
[2]http://blog.csdn.net/sunshine1314/archive/2008/04/20/2309655.aspx
[3]http://topic.csdn.net/t/20061207/10/5212742.html#
[4]http://elanso.com/ArticleModule/LmT3M6M6HGKzTDQcPKMGKAIi.html
[5]关于字节序与编码的关联请参考http://elanso.com/ArticleModule/LmT3M6M6HGKzTDQcPKMGKAIi.html

你可能感兴趣的:(C中的位域)