大小端轉換定義結構體的技巧

這篇文章是我在csdn博客發佈的,由於csdn支持markdown因此打算轉移博客,可是markdown編輯器對linux firefox支持度不是很好,所以放棄csdn博客。暫時先搬到這裏,等哪天本身的博客站建好了,好一塊兒搬家。linux

=============================================================markdown

 

       這兩年一直在寫協議分析和報文填充相關內容。由於PC機是小端(Little Endian),網絡序是大端(Big Endian),在寫代碼的時候必須考慮到大小端轉換的問題,不然網卡或者網絡設備會解析錯誤。網上的總結分析甚少,大部分都還處於糾結於大小尾分辨的層次。今天我就來深度分析一下大小尾問題。網絡

        首先,不論是哪種數據存放方式,對於一個單字節的數據,存儲方式都是同樣的。好比uint8_t類,char類在大端和小端均可以不通過轉換直接讀取。編輯器

        而對於兩字節及以上字節大小的數據,大小端的存儲方式就有區別了。大端(網絡序)的存儲方式與咱們的閱讀邏輯是相同的。大端的存儲方式是高位保存在低地址中,小端(常見的PC機)的存儲方式則是高位存高地址。測試

        舉個例子就明白了,好比有個數字0x12345678,存儲到內存至少須要4字節,按照大端的存儲方式,則是0x12 0x34 0x56 0x78,與咱們的閱讀邏輯一致;而小端的存儲方式則是0x78 0x56 0x34 0x12。記住大端存儲方式(網絡序)與咱們的閱讀邏輯相同就能夠了。ui

       那麼根據上面的分析,咱們就會發現,大小端基於內存的顛卻是以字節爲單位的,不是以每一位爲單位的,這一點在具體的代碼實現中尤爲重要。firefox

       假定咱們有一個uint16_t類型的數據,在小端機器上面處理(如今的pc機基本都是小端機)須要轉換成大端怎麼轉換呢?前一字節與後一字節位置換。內存

       假定咱們有一個uint32_t類型的數據,在小端機器上面處理(如今的pc機基本都是小端機)須要轉換成大端怎麼轉換呢?前一字節與後一字節置換,中間兩個字節也互相置換。博客

       那麼,再進一步,若是有一個結構體,大小32位(4字節),定義以下:數據分析

struct test_u32{

        uint8_t a;

        uint8_t b;

        uint8_t c;

        uint8_t d;

};

        在大小端轉換時,天然是a和d互換,b和c互換。所以,徹底能夠在小端定義結構體時,就定義成以下結構:

struct test_u32{

        uint8_t d;

        uint8_t c;

        uint8_t b;

        uint8_t a;

};

        這樣在進行網絡通訊時就能省去了大小端轉換這個步驟,在大小端轉換較多的狀況下,經過改變結構體定義的方式儘量省去大小端轉換的步驟,對於數據分析和網絡傳輸的速率提高是明顯的。

        那麼接下來再考慮一下狀況,若是上述結構體在大端機定義以下:

struct test_u32_2{

        uint8_t a;

        uint16_t b;

        uint8_t c;

};

        若是隻是在小端機定義成以下形式,會有什麼影響嗎?

struct test_u32_2{

        uint8_t c;

        uint16_t b;

        uint8_t a;

};

        明顯的,a和c因爲他們的定義不大於1字節,所以a和c的數據不會有錯。而因爲b大於1字節了,因此仍然須要進行大小端轉換。這裏就是一個陷阱。


        另外在實際的網絡私有協議定義中,不可能全部的數據都是基於8位(1字節)對齊,有些數據可能只佔1位,有些數據可能佔35位,遇到這種狀況該如何處理呢?

        首先,若是是以下狀況,n個變量瓜分一個字節:

struct test_u8{

        uint8_t a:1;

        uint8_t b:3;

        uint8_t c:4;

};

        此時變量a佔1位,變量b佔3位,變量c佔4位,他們共享一個字節。一個字節是8位,在內存中的存儲方式是「abbbcccc」(每一個字符表明該位存儲着哪一個變量的數據)

        對於這種狀況,能夠直接在小端定義中進行置換:

struct test_u8{

        uint8_t  c:4;

        uint8_t b:3;

        uint8_t a:1;

};

        在小端如此定義,內存中的數據存儲方式依然是abbbcccc。

        那麼再討論一個問題,若是在網絡序機器中定義以下結構體,小端中也能夠如上一例子直接置換嗎?

struct test_u16{

        uint16_t a:3;

        uint16_t b:1;

        uint16_t c:12;

};

        這個時候我會想固然的認爲能夠置換成以下形式,可是打印結果告訴我出錯了。

struct test_u16{

        uint16_t c:12;

        uint16_t b:1;

        uint16_t a:3;

};

        好比我在置換後的結構體中,令a=1,而後按照uint16_t類型輸出該結構體數據,按照預想狀況應該是0x2000,可是實際打印數據是0x0100。根據打印結果推緣由,出錯理由以下。

        1.在大端中,數據保存方式應當以下:aaabcccc cccccccc。那麼若是a=1,b=0,c=0,數據當是0010000 00000000,也就是0x2000。可是如今存儲內容成了00000001 00000000,變成了0x0100,那麼此時在內存中,數據保存形式爲xxxxxaaa xxxxxxxx (x表示未知是b仍是c的存儲位),通過測試,如此錯誤定義結構體,使得原本的數據存儲方式變爲了ccccbaaa cccccccc 。爲何?這個須要本身畫圖想一下,注意大小端的轉換是以字節爲單位轉換而不是以位爲單位轉換的。

所以須要修改定義爲:

struct test_u16{

        uint8_t c_1:4;

        uint8_t b:1;

        uint8_t a:3;

        uint8_t c_2;

};

這時令a=1,b=0,c_1=0,c_2=0,結構體安裝uint16_t輸出即是0x2000。         結論:在大小端處理時遇到不是整本身的變量,定義結構體以uint8_t爲單位定義,對於超過一字節又不足兩字節的變量,要拆成兩部分處理。

相關文章
相關標籤/搜索