對於一個ARM系統來說,32位元據如int的訪問必須是4位元組對齊的,16位就要2位元組對齊(struct 結構中都是int16_t,int32_t可不需要添加__packed,如果有偶數個char型也可以不添加,因為可以按半字對齊,如str中有5個char型,不加則為6,加則為5,但也破壞了邊界對齊的形態,會增加讀取效率),否則就會有Data-Abort的異常。因此在一個struct當中,並不能保證如果按照正常順序寫下來,這些變數就一定是對齊的。例如struct
a { char a; int b;};那麼b如果緊跟著a,就不會是4位元組對齊的。編譯就對於這些沒有對齊的重新安排他們的順序使得他們按要求對齊。
5.5.4. __packed 結構與單個 __packed 欄位
最佳化已壓縮的 struct 時,編譯器嘗試推算每個欄位的對齊以改善訪問。 但是,編譯器並非總是可以推算 __packed struct 中每個欄位的對齊。
與之相對的是,將 struct 中的單個欄位聲明為 __packed 時,可以確保對 struct 中自然對齊成員的快速存取。
因此,需要使用壓縮結構時,建議您始終壓縮結構中的單個欄位,而不是整個結構本身。
Note
將 struct 的單個未對齊欄位聲明為 __packed 還具有其他的優點:程式員可以更清楚的看出 struct 的哪些欄位沒有對齊。
有關不壓縮 struct、壓縮整個 struct 和壓縮 struct 的單個欄位三者之間的差別,將使用Table 5.10 中顯示的三個 struct 的執行來加以說明。
在第一個執行中,struct 沒有壓縮。 在第二個執行中,將整個結構 mystruct 限定為 __packed。
在第三個執行中,從 mystruct 結構中刪除了__packed 屬性,並且將單個未對齊欄位聲明為 __packed。
Table 5.10. 未壓縮 struct、壓縮的 struct 和具有單獨壓縮欄位的 struct 的 C 代碼
| 未壓縮 struct |
__packed struct |
__packed 欄位 |
struct foo{ char one; short two; char three; int four;} c; |
__packed struct foo{ char one; short two; char three; int four;} c; |
struct foo{ char one; __packed short two; char three; int four;} c; |
Table 5.11 是編譯器為Table 5.10 中的每個樣本實現產生的機器代碼的反組譯碼,其中,每個實現的
C 代碼均使用選項 -O2 進行了編譯。
Note
-Ospace 和 -Otime 編譯器選項控制對未對齊元素的訪問是內聯進行還是通過函數調用。
使用 -Otime 將導致內聯未對齊訪問,而使用 -Ospace則導致調用函數來進行未對齊訪問。
Table 5.11. 未壓縮 struct、壓縮的 struct 和具有單獨壓縮欄位的 struct 的反組譯碼
| 未壓縮 struct |
__packed struct |
__packed 欄位 |
; r0 contains address of c; char oneLDRB r1, [r0, #0]; short twoLDRSH r2, [r0, #2]; char threeLDRB r3, [r0, #4]; int fourLDR r12, [r0, #8] |
; r0 contains address of c; char oneLDRB r1, [r0, #0]; short twoLDRB r2, [r0, #1]LDRSB r12, [r0, #2]ORR r2, r12, r2, LSL #8; char threeLDRB r3, [r0, #3]; int fourADD r0, r0, #4BL __aeabi_uread4 |
; r0 contains address of c; char oneLDRB r1, [r0, #0]; short twoLDRB r2, [r0, #1]LDRSB r12, [r0, #2]ORR r2, r12, r2, LSL #8; char threeLDRB r3, [r0, #3]; int fourLDR r12, [r0, #4] |
在Table 5.11 的未壓縮 struct 的反組譯碼中,編譯器始終訪問位於對齊字或半字地址上的資料。
編譯器可以執行此操作是因為填充了 struct,因此 struct 的每個成員均位於自然大小邊界上。
在Table 5.11 中的 __packed struct 反組譯碼中,預設情況下,欄位 one 和 three 在其自然大小邊界上對齊,因此編譯器可以進行對齊訪問。
對可以將其標識為對齊的欄位,編譯器始終執行對齊字或半字的訪問。 對於未對齊欄位 two,編譯器使用多重對齊記憶體訪問 (LDR/STR/LDM/STM),與固定移位和屏蔽組合在一起,用於訪問記憶體中的正確位元組。
編譯器調用 AEABI 運行時常式 __aeabi_uread4 以讀取未知對齊的無符號字,從而訪問欄位 four,原因為無法確定該欄位是否位於其自然大小邊界上。
在Table 5.11 中具有單獨壓縮欄位的 struct 的反組譯碼中,對欄位 one、two 和 three 的訪問方式,與在將整個 struct 限定為 __packed 時的訪問方式相同。
但是,與壓縮整個 struct 的情況相反,編譯器對欄位 four 進行字對齊訪問,因為結構中的 __packed
short 的存在可以協助編譯器確定欄位 four 位於其自然大小邊界上。