為了提高記憶體訪問效率,降低處理器從記憶體讀取資料的開支,各種資料型別的物件並不是連續存放在記憶體中的任意起始位址上的,而是盡可能的對齊到機器字長上面。編譯器出於優化的考慮,會給各種資料型別的變數在記憶體的儲存方式施加限制,我們可以從兩個方面來考慮這個問題,乙個是對齊(alignment),另乙個就是間距(padding)。
各個變數在記憶體中的起始位址能夠被變數所屬型別的寬度所整除。比如說佔據2個位元組的short
型別的位址應該是偶數,佔據4個位元組的int
和float
應該放到能被4整除的起始位址上,佔據8個位元組的double
和long
應該放置在能夠被8整除的起始位址上(注意,資料型別的寬度在 c/c++ 中並沒有被嚴格定義,通常來說32位應用程式中int
和long
都是4個位元組的,64位應用程式中int
為4位元組,long
是8位元組,某些編譯器可能會有與此不同的其他實現)。而char
型別由於只有乙個位元組,所以他存放在**都可以。
指標變數同樣是要對齊的,由於指標變數的寬度和位址匯流排的寬度一致,因此32位應用程式中指標變數總是對齊到能被4整除的位元組上,而64位應用程式中指標變數則存放到可以被8整除的起始位址處。
為了保證各種資料型別的變數都很好地對齊到機器字長上,變數間的間距是另乙個簡單有效的手段。連續定義的變數在記憶體中可能並不連續,他們之間會存在乙個不被使用的間距(由於兩個變數都要遵循前面說的對齊規則,他們之間自然而然就會有間距)。比如:
int
*p =
null
;int n =0;
double f =
0.0;
首先最前面的指標變數p毫無疑問佔據乙個字長(4位元組或者8位元組),後面的int
型別的變數 n 的開始於乙個新的字,其首位址必然可以被4整除。緊隨其後定義的double
型別變數 f 的起始位址則應該被8整除,因此他們之間就會出現乙個4位元組的間距,實際的存放方式類似於下面這樣:
int
*p =
null
;int n =0;
char padding[4]
=;double f =
0.0;
有些情況下,變數定義的順序也會影響他們之間的記憶體間距:
char c =0;
int n =0;
double f =
0.0;
因為字元型別的 c 可以存放在任意位置,因此他和後面的int
型別變數之間的間距無法判斷,從最小的0到最大的7位元組都有可能,這種不確定性也導致 n 和 f 之間的填充大小不能判斷,他們可能是0也可能是4位元組。同樣是定義三個變數,換乙個順序對記憶體的占用就會完全不同:
double f =
0.0;
int n =0;
char c =
0;
此時第乙個double
型別的變數首位址應該被8整除,然後它本身佔據8個位元組。緊隨其後的int
型別的變數n對齊條件自然滿足,佔據4個位元組。最後字元變數c佔據乙個位元組,不需要遵循對齊條件,所以會緊挨著變數n存放。因此上面三個變數共佔據13位元組的記憶體空間。
結構體變數的首位址可以被該結構體中寬度最長的型別的位元組寬度所整除,結構體變數的首位址就是其第乙個成員變數的位址。
每乙個結構體成員都要受到對齊規則的限制,對齊寬度取該成員變數的寬度和預設對齊寬度中的較小值,對於32位應用程式預設對齊寬度是4位元組,對於64位應用程式對齊寬度是8位元組。
結構體變數所佔據的總位元組數應該可以被最長型別寬度所整除,相當於在結構體的末尾填充一定數量的位元組。
比如說下面這個例子:
struct s1
;struct s2
;
s1
結構體包含乙個字元型別和乙個雙精度浮點型別,其中最長型別寬度為8位元組,因此結構體的起始位址應該被8整除,第乙個成員 c 佔據1位元組,後面填充7個位元組,然後存放第二個成員 i ,佔據8個位元組。結構體的總大小是16個位元組,可以被8整除不必再做填充。
s2
結構體第乙個成員 c1 佔據1個位元組,後面填充7個位元組,然後存放佔據16個位元組的成員結構體s,接下來存放佔據乙個位元組的變數 c2,然後填充7位元組,最後存放佔據8個位元組的 c3 變數。把他們加在一起可知總長度是40位元組,可以被8整除,末尾不必填充。
實際上如果理解了上面所說對齊規則的最終目的,並沒有必要去刻意記憶這些規則也可以得到結構體變數在記憶體中的正確布局。
可以通過預處理指令#pragma pack(n)
來修改預設對齊寬度,其中n可以等於1,2,4,8,16。
在visual studio 中,通過專案屬性中的 c/c++ 選項,在裡面的**生成一欄下面有結構成員對齊這一選項,可以修改這個來設定整個專案的預設對齊引數。不過通常來說沒必要修改這個選項。
在某些特殊場合,比如解析具有特定檔案頭二進位制檔案時,可以通過設定#pragma pack(1)
來強制結構體中各個成員變數連續存放。
結構體的記憶體布局
看 thinking in c 的時候有下面一段 include using namespace std struct a struct b void b f int main 作者說sizeof a 很容易理解,但是sizeof b 不是0有些不正常。他解釋到這是因為c 不允許兩個object的記...
結構體對齊 結構體內存布局
在c語言中,可以通過 pragma pack n 來指定結構體按n位元組對齊 這裡的n是2的較小整數次冪 如果程式設計者不指定對齊位元組數,那麼預設的會按照結構體中最長那一項對齊,如在64位作業系統中,當結構體中出現 void long 型別,則必然是按照8位元組對齊 當最大的是int,那麼就按照4...
熟悉結構體 結構體的記憶體對齊
結構的宣告注意問題。結構體成員的訪問 結構變數的成員是通過點操作符 訪問的。如果是結構體指標,則用 結構體的自引用 struct node 結構體內存對齊 為什麼要存在記憶體對齊?1.平台問題 移植原因 不是所有的硬體平台都能訪問任意位址上的任意資料的 某些硬體平台只能在某些位址處取某些特定型別的資...