在求職筆試中,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