天天看點

【問答21】C語言:位域和位元組序

1. 粉絲問題

自己編寫的一個協定相關代碼,位域的值解析和自己想象的有出入。

【問答21】C語言:位域和位元組序

結構體的頭:

【問答21】C語言:位域和位元組序

解析代碼和測試結果:

【問答21】C語言:位域和位元組序

就是說通過函數hexdump()解析出的記憶體是十六進制是 81 83 20 3B …

從資料幀解析出的

該粉絲不明白為什麼解析出的值是0x8。

這個問題其實就是位域的問題和位元組序的問題。

廢話不多說,直接寫個測試代碼

執行結果:

分析:

如下圖所示,紫色部分是位域成員對應的記憶體中的實際空間布局,位址從左到右增加

第一個位元組的0x81指派後,各位域對應的二進制:

【問答21】C語言:位域和位元組序

如上圖多少,記憶體的第1個位元組是0x81,第2個位元組是0x83;

第一個位元組0x81的最低的bit[0]對應fin,bit[3:1]對應rsv,bit[7:4]對應opcode;

第二個位元組0x83的最低bit[0]對應mask,bit[7:1]對應payload。

是以結果顯而易見。

有些資訊在存儲時,并不需要占用一個完整的位元組, 而隻需占幾個或一個二進制位。

例如在存放一個開關量時,隻有0和1 兩種狀态, 用一位二進位即可。為了節省存儲空間,并使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。

所謂“位域”是把一個位元組中的二進位劃分為幾個不同的區域,并說明每個區域的位數。

每個域有一個域名,允許在程式中按域名進行操作。這樣就可以把幾個不同的對象用一個位元組的二進制位域來表示。一、位域的定義和位域變量的說明位域定義與結構定義相仿,其形式為:

其中位域清單的形式為:

如粉絲所舉的執行個體:

位域變量的說明與結構變量說明的方式相同。 可采用先定義後說明,同時定義說明或者直接說明這三種方式。例如:

說明data為bs變量,共占兩個位元組。其中位域a占8位,位域b占2位,位域c占6位。對于位域的定義尚有以下幾點說明:

一個位域必須存儲在同一個位元組中,不能跨兩個位元組。

如一個位元組所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:

在這個位域定義中,a占第一位元組的4位,後4位填0表示不使用,b從第二位元組開始,占用4位,c占用4位。

位域可以無位域名,這時它隻用來作填充或調整位置。無名的位域是不能使用的。

例如:

從以上分析可以看出,位域在本質上就是一種結構類型, 不過其成員是按二進位配置設定的。

這是位域操作的表示方法,也就是說後面加上“:1”的意思是這個成員的大小占所定義類型的1 bit,“:2”占2 bit,依次類推。當然大小不能超過所定義類型包含的總bit數。

一個bytes(位元組)是8個 bit(二進制位)。例如你的結構體中定義的類型是u_char,一個位元組,共8個bit,最大就不能超過8。

32位機下,short是2位元組,共16bit,最大就不能超過16,int是4位元組,共32bit,最大就不能超過32.

依次類推。

位域定義比較省空間。

例如你上面的結構,定義的變量類型是u_char,是一位元組類型,即8bit。

fc_subtype占了4bit,fc_type占2bit,fc_protocol_version占2bit,共8bit,正好是一個位元組。

其他八個成員,各占1bit,共8bit,正好也是一個位元組。

是以你的結構的大小如果用sizeof(struct frame_control)計算,就是2bytes。

計算機硬體有兩種儲存資料的方式:大端位元組序(big endian)和小端位元組序(little endian)。

大端位元組序:高位位元組在前,低位位元組在後,這是人類讀寫數值的方法。

小端位元組序:低位位元組在前,高位位元組在後。

0x1234567的大端位元組序和小端位元組序的寫法如下圖。

【問答21】C語言:位域和位元組序

答案是,計算機電路先處理低位位元組,效率比較高,因為計算都是從低位開始的。是以,計算機的内部處理都是小端位元組序。

但是,人類還是習慣讀寫大端位元組序。是以,除了計算機的内部處理,其他的場合幾乎都是大端位元組序,比如網絡傳輸和檔案儲存。

計算機處理位元組序的時候,不知道什麼是高位位元組,什麼是低位位元組。它隻知道按順序讀取位元組,先讀第一個位元組,再讀第二個位元組。

如果是大端位元組序,先讀到的就是高位位元組,後讀到的就是低位位元組。小端位元組序正好相反。

了解這一點,才能了解計算機如何處理位元組序。

處理器讀取外部資料的時候,必須知道資料的位元組序,将其轉成正确的值。然後,就正常使用這個值,完全不用再考慮位元組序。

即使是向外部裝置寫入資料,也不用考慮位元組序,正常寫入一個值即可。外部裝置會自己處理位元組序的問題。

仍然用上面的例子,但是做如下修改

由結果可知,收到的0x8183這個值與對應的的二進制關系:

【問答21】C語言:位域和位元組序

如上圖多少,記憶體的第1個位元組是0x83,第2個位元組是0x81【和前面的例子不一樣了,因為我們是直接指派0x8183,而該常數是小位元組序,是以低位元組是0x83】;

可見:

低位元組83給了 fin+rsv+opcode

是以,這說明了一口君的ubuntu是小端位元組序。

繼續将結構體做如下修改,當位域成員大小加一起不夠一個整位元組的時候,驗證各成員在記憶體中的布局。

【問答21】C語言:位域和位元組序

記憶體中形式如下:

【問答21】C語言:位域和位元組序

如果修改fin的值為0:

執行結果如下:

【問答21】C語言:位域和位元組序
【問答21】C語言:位域和位元組序

大家遇到類似問題的時候,一定要寫一些執行個體去驗證,對于初學者來說,建議多參考上述執行個體。

繼續閱讀