1. 程式人生 > >C/C++ 位元組序/位域(Bit-fields)之我見

C/C++ 位元組序/位域(Bit-fields)之我見

前言

很早想說說這個問題了,經常也會有很多公司拿位域出來考人,呵呵要真的想弄清楚還要一點點的分析。

這裡先看看網宿的一道筆試題目,這道題目我之前是複製網上的,結果不對,修改了一下,可以正確運行了,謝謝(imafish_i )提醒:

有點難度的一道題目,其實理解的也很簡單。

位域(Bit-fields)分析

位域是c++和c裡面都有的一個概念,但是位域有一點要注意的有很多問題我們一樣樣的看:

大端和小端位元組序


這個很簡單,就是起始點該怎麼確定。
先看一個程式:

  1. union {
  2.     struct  
  3.     {
  4.         unsigned char a1:2;
  5.         unsigned char a2:3;
  6.         unsigned char a3:3;
  7.     }x;
  8.     unsigned char b;
  9. }d;
  10. int main(int argc, char* argv[])
  11. {
  12.     d.b = 100;
  13.     return
     0;
  14. }

那麼x的a1,a2,a3該怎麼分配值,100的二進位制是:0110 0100,那麼a1到a3是不是就是依次取值恩?
不是!
我們先看看100分配位的低端是左邊的0還是右邊的0?很明顯是右邊的0,那麼我們再看a1到a3的分配是從低端到高階的
那麼,對應的應該是
<<<<<<--記憶體增大
a3   a2  a1
011  001 00


記憶體增大之所以這麼寫是因為,011是在高位!
而不是通常認為的的:
a1   a2  a3
011  001 00

還有一個情況多見就是一個二進位制的數字轉化為點分十進位制數值,如何進行,這裡涉及到大端還是小端的問題,上面沒有涉及,主要是因為上面是一個位元組,沒有這個問題,多個位元組就有大端和小端的問題了,或許我們應該記住這一點就是,在我們的計算機上面,大端和小端都是以位元組為準的

,當然嚴格來說更應該以位為準不是嗎?具體可以參考維基百科上面的一篇文章,他給出了一個以位為準的大小端序的圖:

下面研究位元組為單位的大小端序,繼續看程式碼吧,如下:

  1. int main(int argc, char* argv[])
  2. {
  3.     int a = 0x12345678;
  4.     char *p = (char *)&a;
  5.     char str[20];
  6.     sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
  7.     printf(str);
  8.     return 0;
  9. }

這個程式假設是小端位元組序,那麼結果是什麼?
我們看看應該怎麼放置呢?
每個位元組8位,0x12345678分成4個位元組,就是從高位位元組到低位位元組:12,34,56,78,那麼這裡該怎麼放?如下:
---->>>>>>記憶體增大
78 56 34 12

因為這個是小端,那麼小記憶體對應低位位元組,就是上面的結構。

接下來的問題又有點迷糊了,就是p怎麼指向,是不是指向0x12345678的開頭--12處?不是!12是我們所謂的開頭,但是不是記憶體

的開始處,我們看看記憶體的分佈,我們如果瞭解p[0]到p[1]的操作是&p[0]+1,就知道了,p[1]地址比p[0]地址大,也就是說p的地址

也是隨記憶體遞增的!

12 ^ p[3]
    |
34 | p[2]
    |
56 | p[1]
    |
78 | p[0]
記憶體隨著箭頭增大!同時小端儲存也是低位到高位在記憶體中的增加!
這樣我們知道了記憶體怎麼分佈了

那麼:

  1. sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);

str就是這個結果了:
120.86.52.18

那麼反過來呢?

  1. int main(int argc, char* argv[])
  2. {
  3.     int a = 0x87654321;
  4.     char *p = (char *)&a;
  5.     char str[20];
  6.     sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
  7.     printf(str);
  8.     return 0;
  9. }

依舊是小端,8位是一個位元組那麼就是這樣的啦:

87 ^ p[3]
     |
65 | p[2]
    |
43 | p[1]
    |
21 | p[0]

結果是:
33.67.101.-121
為什麼是負的?因為系統預設的char是有符號的,本來是0x87也就是135,大於127因此就減去256得到-121
那麼要正的該怎麼的弄?
如下就是了:

  1. int main(int argc, char* argv[])
  2. {
  3.     int a = 0x87654321;
  4.     unsigned char *p = (unsigned char *)&a;
  5.     char str[20];
  6.     sprintf(str,"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
  7.     printf(str);
  8.     return 0;
  9. }

用無符號的!
結果:
33.67.101.135

位域的符號(正負)

看完大端和小端以後,再看看位域的取值的問題,上面我們談到了一些,首先就是位域是按照位來取值的跟我們的int是32位char是8

位一樣,很簡單,但是,要注意一點就是位域也有正負,指有符號屬性的,就是最高位表示的,也會涉及到補碼這個一般被認為非常

噁心的東西,看看程式吧:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. int main(int argc, char** argv)
  5. {
  6.     union
  7.     {
  8.         struct
  9.         {
  10.             unsigned char a:1;
  11.             unsigned char b:2;
  12.             unsigned char c:3;
  13.         }d;
  14.         unsigned char e;
  15.     } f;
  16.     f.e = 1;
  17.     printf("%d/n",f.d.a);
  18.     return 0;
  19. }

<小端>
那麼輸出是什麼?
換一下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. int main(int argc, char** argv)
  5. {
  6.     union
  7.     {
  8.         struct
  9.         {
  10.             char a:1;
  11.             char b:2;
  12.             char c:3;
  13.         }d;
  14.         char e;
  15.     } f;
  16.     f.e = 1;
  17.     printf("%d/n",f.d.a);
  18.     return 0;
  19. }

輸出又是什麼?

小端的話,那麼,再d.a上面分得1,而這個是無符號的char,那麼前者輸出是1,沒有問題,第二個輸出是-1,哈哈。
為什麼?
第二個是無符號的,就一個位分得1,那麼就是最高位分得1,就是負數,負數用的補碼,實際的值是取反加1,就是0+1=1,再取符

號負數,就是-1.

整型提升

最後的列印是用的%d,那麼就是對應的int的列印,這裡的位域肯定要提升,這裡有一點,不管是提升到有符號還是無符號,都是自

己的符號位來補充,而不改變值的大小(這裡說的不改變值大小是用相同的符號屬性來讀取),負數前面都補充1,正數都是用0來補充

,而且也只有這樣才能保證值不變,比如,char提升到int就是前面補充24個char的最高位,比如:

  1.  char c = 0xf0; 
  2.  int p = c;
  3.  printf("%d %d/n",c,p);

輸出:-16 -16
p實際上就是0xfffffff0,是負數因此就是取反加1得到
c是一個負數那麼轉化到x的時候就是最高位都用1來代替,得到的數不會改變值大小的。
再看:

  1.  char c = 0xf0;
  2.  unsigned int x = c;
  3.  printf("%u/n",x);

得到的結果是4294967280,也就是0xfffffff0,記住,無符號用%u來列印。

地址不可取


最後說的一點就是位域是一個位元組單元裡面的一段,是沒有地址的!

附錄

最後附上《The C Book》這本書的一段說法:
While we're on the subject of structures, we might as well look at bitfields. They can only be declared inside a

structure or a union, and allow you to specify some very small objects of a given number of bits in length. Their

usefulness is limited and they aren't seen in many programs, but we'll deal with them anyway. This example should

help to make things clear:

  1. struct {
  2.       /* field 4 bits wide */
  3.       unsigned field1 :4;
  4.       /*
  5.        * unnamed 3 bit field
  6.        * unnamed fields allow for padding
  7.        */
  8.       unsigned        :3;
  9.       /*
  10.        * one-bit field
  11.        * can only be 0 or -1 in two's complement!
  12.        */
  13.       signed field2   :1;
  14.       /* align next field on a storage unit */
  15.       unsigned        :0;
  16.       unsigned field3 :6;
  17. }full_of_fields;

Each field is accessed and manipulated as if it were an ordinary member of a structure. The keywords signed and

unsigned mean what you would expect, except that it is interesting to note that a 1-bit signed field on a two's

complement machine can only take the values 0 or -1. The declarations are permitted to include the const and

volatile qualifiers.

The main use of bitfields is either to allow tight packing of data or to be able to specify the fields within some

externally produced data files. C gives no guarantee of the ordering of fields within machine words, so if you do

use them for the latter reason, you program will not only be non-portable, it will be compiler-dependent too. The

Standard says that fields are packed into ‘storage units’, which are typically machine words. The packing order, and

whether or not a bitfield may cross a storage unit boundary, are implementation defined. To force alignment to a

storage unit boundary, a zero width field is used before the one that you want to have aligned.

Be careful using them. It can require a surprising amount of run-time code to manipulate these things and you can

end up using more space than they save.

Bit fields do not have addresses—you can't have pointers to them or arrays of them.

最後

瞭解了這些我想網宿的那道題目也很簡單了。希望大家指正。