Endian Bitfiled
生活随笔
收集整理的這篇文章主要介紹了
Endian Bitfiled
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Endianess是個比較tricky的問題,特別是當數據在不同類型間轉換時。?
先看看在一臺32位的little-endian機器上,以下代碼的行為:?
C代碼?? #include?<stdio.h>?//?memcpy?? #include?<stdlib.h>?//?printf?? ?? typedef?struct?{?? ????char?a;?? ????char?b;?? ????char?c;?? ????char?d;?? }?st;?? ?? void?main()?{?? ????st?s;?????? ????memcpy(&s,?"RednaxelaFX",?sizeof(s));?? ????printf("sizeof(s)?=?%d\n",?sizeof(s));?//?4?? ????printf("%X\n",?s);?//?6E646552?? ????printf("%c,?%c,?%c,?%c\n",?s.a,?s.b,?s.c,?s.d);?//?R,?e,?d,?n?? }??
在32位x86上有4字節的對齊,正好4個char就是4字節于是st中沒有padding??梢钥吹?#xff0c;struct中的成員是按聲明順序從低地址到高地址排列的。C99規范的6.7.2.1規定了這點。一個數組是一塊連續的、有序的空間,而一個字符串是一個char數組,所以可以看到s.a, s.b, s.c, s.d跟字符串(char數組)的開頭4個字符(字節)對應:'R' 'e' 'd' 'n'(52 65 64 6E)分成4段:(| 52 | 65 | 64 | 6E |)分別對應s.a到s.d。?
但將該struct解釋為一個32位的整型,并以十六進制的方式顯示出來,則會發現字節的順序顛倒了過來:?
原本'R' 'e' 'd' 'n'的十六進制表示是52 65 64 6E;轉換為一個整型之后,則變為0x6E646552,字節的順序正好反了過來。注意是字節而不是位的順序反了過來,字節內的位的順序依然保持不變。?
這體現了little-endian機器上對數據解釋方式的不同。在內存中的數據在參與運算前會先加載到寄存器中,字節序(endian)的差異就在這一步上:如果是big-endian,則讀到寄存器的數據的字節序跟內存中的一樣;反之如果是little-endian,則讀到寄存器的數據的字節序跟內存中的相反。?
說“相反”,到底在多大的范圍內“相反”呢?這就要看運算涉及的數據類型(特指原始數據類型dword/word/byte等)的寬度了:數據類型有多寬,就在多少字節間需要將字節順序反轉過來。如果上面的st中不是含有4個char而是含有兩個short,那么在32位x86上的執行結果就會變成:?
C代碼?? #include?<stdio.h>?? #include?<stdlib.h>?? ?? typedef?struct?{?? ????short?a;?? ????short?b;?? }?st;?? ?? void?main()?{?? ????st?s;?????? ????memcpy(&s,?"RednaxelaFX",?sizeof(s));?? ????printf("sizeof(s)?=?%d\n",?sizeof(s));?//?4?? ????printf("%X\n",?s);?//?6E646552?? ????printf("%X,?%X\n",?s.a,?s.b);?//?6552,?6E64?? }??
也就是把'R' 'e' 'd' 'n'(52 65 64 6E)分成兩段(| 52 65 | 64 6E |),然后將字節的順序反轉過來解釋為整型數字(0x6552,0x6E64)。直接解釋為一個32位整型的時候,'R' 'e' 'd' 'n'(52 65 64 6E)分成一段(| 52 65 64 6E |),并在這一段內反轉字節順序得到0x6E646552。?
前面用4個char為例時,因為每個單元的數據本身就只有1字節,反轉不反轉沒有差別,所以無論是在big-endian還是在little-endian機器上執行的結果都會是一樣的。?
再次強調:字節序只影響解釋數據時字節間的順序,不影響每個字節內的位的順序。?
===========================================================================?
那么結合C struct里的bitfield又會怎樣的??
還是在C99規范的6.7.2.1節里,規范規定了可以對struct中的field顯式指定寬度(以bit為單位);顯式指定了寬度的field被稱為bit-field。規范中同一小節中的第10點有如下說明:?
引用 An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined.?The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined.?The alignment of the addressable storage unit is unspecified.
根據該說明,規范留下了許多故意未定義的地方,包括紅字標出的:同一單元內排列bit-field的順序未定義。所以我們無法根據規范確定同一組bit-field到底哪個在前哪個在后。?
雖然如此,具體到某個機器上某個編譯器所編譯出來的結果還是需要解釋的。這里以32位x86搭配VC9/GCC3.4為例,這兩個編譯器在這個例子中行為一樣。觀察下面代碼?
C代碼?? #include?<stdio.h>?? #include?<stdlib.h>?? ?? typedef?struct?{?? ????int?a:5;?? ????int?:2;?? ????int?b:2;?? }?bitstruct;?? ?? void?main()?{?? ????bitstruct?b;?? ????memcpy(&b,?"RednaxelaFX",?sizeof(b));?? ????printf("sizeof(b)?=?%d\n",?sizeof(b));?//?4?? ????printf("%X\n",?b);?//?6E646552?? ????printf("%X,?%X\n",?b.a,?b.b);?//?FFFFFFF2,?FFFFFFFE?? ????printf("%d,?%d\n",?b.a,?b.b);?//?-14,?-2?? }??
例子中的bitstruct中有3個bit-field,第一個是名字為a的5位帶符號整型,第二個是匿名的2位帶符號整形,第三個是名字為b的2位帶符號整型,加起來一共是9位,但32位x86上有4字節對齊的要求,所以經過padding后,bitstruct的寬度是4字節(32位)。從存儲單元的角度看,1個dword(32位)就是1個存儲單元,bitstruct中包含一個存儲單元,并且其中的3個成員都在這個存儲單元中。bitstruct.a占有“前”5位,匿名field占有緊接著的2位,然后bitstruct.b占有接下來的2位(注意“前”到底是從哪邊開始在規范中并沒有定義)。?
這個寬度與前面兩個例子中的st一樣,當然執行到printf("%X\n", b);這句的時候結果也一樣。關鍵就在這其中的bit-field是如何解釋成-14和-2的。?
從結果來看,可以知道:bitstruct的3個field都在同一個存儲單元內,并且由于x86是little-endian的,數據從內存讀到寄存器之后字節序就反了過來,高位字節到低位字節的順序是“從右向左”;對應的,解釋bitstruct中的各field時也從右向左來讀。?
寄存器中的b:?
'n' 'd' 'e' 'R'?
6E? 64? 65? 52?
用二進制表示就是:?
01101110 01100100 01100101 01010010?
“從右向左”來數寬度,最靠右的5位是b.a,其左邊2位是匿名field,然后再左邊2位是b.b。注意:從右向左數的是寬度,不是數據內容的位的順序。?
那么就有(二進制):?
b.a: 10010?
b.b: 10?
由于這兩個field是帶符號的整型,讀出來的時候需要做帶符號擴展到一個int,于是它們就分別擴展到(十六進制):?
b.a: FFFFFFF2?
b.b: FFFFFFFE?
換回用普通的帶符號十進制表示也就是:?
b.a: -14?
b.b: -2?
如果還是覺得沒轉過腦筋來,那么這樣看:實際由編譯器編譯出來會像這樣:(只是用于表現概念)?
C代碼?? bitstruct?b;?? int?temp;?? //?assign?to?b,?make?some?changes,?whatever...?? ?? //?here's?what's?supposed?to?be?"temp?=?b.a":?? temp?=?(int)b;?? temp?<<=?27;?//?27?==?32?-?5?? temp?>>=?27;?//?27?==?32?-?5?? ?? //?and?here's?what's?supposed?to?be?"temp?=?b.b":?? temp?=?(int)b;?? temp?<<=?23;?//?23?==?32?-?5?-?2?-?2?? temp?>>=?30;?//?30?==?32?-?2??
注意:?
1. temp = (int)b;這里,把bitstruct里的數據解釋為單個32位整型時,little-endian的作用就體現出來了;在4個字節的范圍內字節的順序需要反轉過來。?
2. 左移27位再右移27位與直接拿0x01F來做mask是不等價的:前者的右移是帶符號的,而后者直接把符號掩蓋掉了。?
--------------------------------------------------------------------------?
需要在說明一次,規范中并沒有規定一定要按照這個順序,只是這個例子選用的機器+編譯器組合中有這樣的行為而已。bit-field在存儲單元內安排的順序如何從一般程序中是看不出來的,因為:?
引用 The unary & (address-of) operator cannot be applied to a bit-field object; thus, there are no pointers to or arrays of bit-field objects.
無法獲得單一bit-field的地址(但可以獲得這個bit-field所在的存儲單元的起始地址),所以一般程序無從得知到底bit-field是從哪邊開始存的;編譯器會生成合適的位移指令序列來得到bit-field的值/對bit-field賦值。程序不應該依賴于bit-field存儲的順序。 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
先看看在一臺32位的little-endian機器上,以下代碼的行為:?
C代碼??
在32位x86上有4字節的對齊,正好4個char就是4字節于是st中沒有padding??梢钥吹?#xff0c;struct中的成員是按聲明順序從低地址到高地址排列的。C99規范的6.7.2.1規定了這點。一個數組是一塊連續的、有序的空間,而一個字符串是一個char數組,所以可以看到s.a, s.b, s.c, s.d跟字符串(char數組)的開頭4個字符(字節)對應:'R' 'e' 'd' 'n'(52 65 64 6E)分成4段:(| 52 | 65 | 64 | 6E |)分別對應s.a到s.d。?
但將該struct解釋為一個32位的整型,并以十六進制的方式顯示出來,則會發現字節的順序顛倒了過來:?
原本'R' 'e' 'd' 'n'的十六進制表示是52 65 64 6E;轉換為一個整型之后,則變為0x6E646552,字節的順序正好反了過來。注意是字節而不是位的順序反了過來,字節內的位的順序依然保持不變。?
這體現了little-endian機器上對數據解釋方式的不同。在內存中的數據在參與運算前會先加載到寄存器中,字節序(endian)的差異就在這一步上:如果是big-endian,則讀到寄存器的數據的字節序跟內存中的一樣;反之如果是little-endian,則讀到寄存器的數據的字節序跟內存中的相反。?
說“相反”,到底在多大的范圍內“相反”呢?這就要看運算涉及的數據類型(特指原始數據類型dword/word/byte等)的寬度了:數據類型有多寬,就在多少字節間需要將字節順序反轉過來。如果上面的st中不是含有4個char而是含有兩個short,那么在32位x86上的執行結果就會變成:?
C代碼??
也就是把'R' 'e' 'd' 'n'(52 65 64 6E)分成兩段(| 52 65 | 64 6E |),然后將字節的順序反轉過來解釋為整型數字(0x6552,0x6E64)。直接解釋為一個32位整型的時候,'R' 'e' 'd' 'n'(52 65 64 6E)分成一段(| 52 65 64 6E |),并在這一段內反轉字節順序得到0x6E646552。?
前面用4個char為例時,因為每個單元的數據本身就只有1字節,反轉不反轉沒有差別,所以無論是在big-endian還是在little-endian機器上執行的結果都會是一樣的。?
再次強調:字節序只影響解釋數據時字節間的順序,不影響每個字節內的位的順序。?
===========================================================================?
那么結合C struct里的bitfield又會怎樣的??
還是在C99規范的6.7.2.1節里,規范規定了可以對struct中的field顯式指定寬度(以bit為單位);顯式指定了寬度的field被稱為bit-field。規范中同一小節中的第10點有如下說明:?
引用 An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined.?The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined.?The alignment of the addressable storage unit is unspecified.
根據該說明,規范留下了許多故意未定義的地方,包括紅字標出的:同一單元內排列bit-field的順序未定義。所以我們無法根據規范確定同一組bit-field到底哪個在前哪個在后。?
雖然如此,具體到某個機器上某個編譯器所編譯出來的結果還是需要解釋的。這里以32位x86搭配VC9/GCC3.4為例,這兩個編譯器在這個例子中行為一樣。觀察下面代碼?
C代碼??
例子中的bitstruct中有3個bit-field,第一個是名字為a的5位帶符號整型,第二個是匿名的2位帶符號整形,第三個是名字為b的2位帶符號整型,加起來一共是9位,但32位x86上有4字節對齊的要求,所以經過padding后,bitstruct的寬度是4字節(32位)。從存儲單元的角度看,1個dword(32位)就是1個存儲單元,bitstruct中包含一個存儲單元,并且其中的3個成員都在這個存儲單元中。bitstruct.a占有“前”5位,匿名field占有緊接著的2位,然后bitstruct.b占有接下來的2位(注意“前”到底是從哪邊開始在規范中并沒有定義)。?
這個寬度與前面兩個例子中的st一樣,當然執行到printf("%X\n", b);這句的時候結果也一樣。關鍵就在這其中的bit-field是如何解釋成-14和-2的。?
從結果來看,可以知道:bitstruct的3個field都在同一個存儲單元內,并且由于x86是little-endian的,數據從內存讀到寄存器之后字節序就反了過來,高位字節到低位字節的順序是“從右向左”;對應的,解釋bitstruct中的各field時也從右向左來讀。?
寄存器中的b:?
'n' 'd' 'e' 'R'?
6E? 64? 65? 52?
用二進制表示就是:?
01101110 01100100 01100101 01010010?
“從右向左”來數寬度,最靠右的5位是b.a,其左邊2位是匿名field,然后再左邊2位是b.b。注意:從右向左數的是寬度,不是數據內容的位的順序。?
那么就有(二進制):?
b.a: 10010?
b.b: 10?
由于這兩個field是帶符號的整型,讀出來的時候需要做帶符號擴展到一個int,于是它們就分別擴展到(十六進制):?
b.a: FFFFFFF2?
b.b: FFFFFFFE?
換回用普通的帶符號十進制表示也就是:?
b.a: -14?
b.b: -2?
如果還是覺得沒轉過腦筋來,那么這樣看:實際由編譯器編譯出來會像這樣:(只是用于表現概念)?
C代碼??
注意:?
1. temp = (int)b;這里,把bitstruct里的數據解釋為單個32位整型時,little-endian的作用就體現出來了;在4個字節的范圍內字節的順序需要反轉過來。?
2. 左移27位再右移27位與直接拿0x01F來做mask是不等價的:前者的右移是帶符號的,而后者直接把符號掩蓋掉了。?
--------------------------------------------------------------------------?
需要在說明一次,規范中并沒有規定一定要按照這個順序,只是這個例子選用的機器+編譯器組合中有這樣的行為而已。bit-field在存儲單元內安排的順序如何從一般程序中是看不出來的,因為:?
引用 The unary & (address-of) operator cannot be applied to a bit-field object; thus, there are no pointers to or arrays of bit-field objects.
無法獲得單一bit-field的地址(但可以獲得這個bit-field所在的存儲單元的起始地址),所以一般程序無從得知到底bit-field是從哪邊開始存的;編譯器會生成合適的位移指令序列來得到bit-field的值/對bit-field賦值。程序不應該依賴于bit-field存儲的順序。 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的Endian Bitfiled的全部內容,希望文章能夠幫你解決所遇到的問題。