1、位段
面試中興的時候,被問到了位段的內容,當時對位段毫不了解,今天就來個總結吧。
首先,位段是結構體為了節省內存的一種定義方式,在計算機網絡中應用比較多,以下舉例說明。
比如,我們現在有三個整形變量,變量的范圍分別為0~15,0~10,0~254,我們知道 unssingned char表示的數字范圍為0~254,所以,我們可以用三個unsigned char類型的成員來保存這三個變量,定義如下:
我們可以計算出上述結構體的內存占用大小為3字節,共24位,我們知道15只需要4位就可以表示,10也只需要4位就可以表示,所以,誕生了位段,上述結構體的代碼我們可以定義如下,並輸出其內存大小:
#include <stdio.h> struct S { unsigned char val1 : 4; unsigned char val2 : 4; unsigned char val3 ; }; int main() { struct S s; s.val1 = 15; s.val2 = 10; s.val3 = 127; printf("sizeof(struct S) = %d \n", sizeof(struct S)); printf("s.val1 = %d \n", s.val1); printf("s.val2 = %d \n", s.val2); printf("s.val3 = %d \n", s.val3); return 0; }
上述代碼運行結果為:
通過位段的定義,結構體的大小由原先的3字節節省到2字節,通過賦值驗證,上述定義完全可以實現變量的需求。在計算機網絡中,TCP的報文封裝應用到了位段。
位段是將一變量類型,通常為char, int, unsigned int , insigned char 類型分成多位來操作,如unsigned int 類型可以分成32位,但是,C語言沒有規定32位從左往右取,還是從右往左取,所以使用位段需要注意移植問題,位段使用盡量不要跨平台。
2、大小端問題
提到位,還有一個比較重要的考察知識點就是大小端問題,大端存儲:就是低字節內容存放在高地址處,高字節內容存放在低地址處。也就是低對高,高對低。小端模式則剛好相反。
如:int val = 0x11223344;
如果為小端存儲模式,內存中的字節存儲方式為 44 33 22 11(從左到右地址由低到高)如果為大端存儲模式,內存中的存儲方式為11 22 33 44。通常,X86架構為小端存儲模式,網絡中的通常使用大端模式,所以在Linux系統中,網絡編程處理數據需要大小端轉換,Linux內核也集成了大小端轉換函數ntohl()和htonl()。下面通過一個實例來說明大小端存儲模式:
#include <stdio.h> int main() { int val1 = 0x11223344; char val2 = (char)val1; printf("%x\n", val2); return 0; }
代碼運行結果為:
上述代碼說明我的編譯器為小端存儲,對於整形數的4字節存儲方式為44 33 22 11 ,低地址存儲0x44,所以,將valu1賦值給val2的char類型只取了低字節,答案為0x44,通常也可以使用這種方式來判斷當前機器為大端存儲,或者小端存儲,以下給出完整驗證代碼:
#include <stdio.h> int main() { int val1 = 1; if (1 == *(char*)(&val1)) { printf("小端模式\n"); } if (0 == *(char*)(&val1)) { printf("大端模式\n"); } return 0; }
當然也可以通過公用體來進行驗證,下面代碼說明:
#include <stdio.h> union UN{ int val1; char ch; }; int main() { union UN un; un.val1 = 1; if (1 == un.ch) { printf("小端模式\n"); } else if (0 == un.ch) { printf("大端模式\n"); } return 0; }
代碼運行結果:
3、位運算優先級
因為平常寫代碼的時候,涉及到優先級問題的時候,通常都可以用括號解決,當時面試的時候忽然被問起,搞的很懵,所以特別強調以下,位運算的優先級:~ > & > ^ > | ,也就是非的運算級大於與大於異或大於或。邏輯運算符的優先級順序也是如此,即:!> && > ||
我們通過位運算來實現大小端的轉換:
#include <stdio.h> int main() { int val = 0x11223344;//小端存儲模式,從低到高地址,內存中單字節內存中存放的內容為44 33 22 11 int convertVal = 0; convertVal = ((val & 0xff) << 24) //取低8位,左移16位,按位&的優先級低於左移運算符的優先級,所以一定要加括號 | ((val & 0xff00) << 8) //取次低8位,左移8位 | ((val & 0xff0000) >> 8) //取次高8位,右移8位 | ((val & 0xff000000) >> 24);//取高8位,右移16位 printf("convertVal = 0x%x",convertVal); return 0; }
代碼運行結果為: