結構體內部儲存中的對齊問題

li27z發表於2016-10-15

舉個例子

我們考慮如下的這個結構體:

struct ALIGN {
        char a;
        int b;
        char c;
};

如果某個機器的整型值長度為4個位元組,並且它的起始儲存位置必須能夠被4整除,那麼這個結構體在記憶體中的儲存將如下圖所示:
這裡寫圖片描述

說明:系統禁止編譯器在一個結構體的起始位置跳過幾個位元組來滿足邊界的對齊要求,因此所有結構體的起始儲存位置必須是結構體中邊界要求最嚴格的資料型別所要求的位置。因此,成員a(最左邊那個方框)必須儲存於一個能夠被4整除的地址。結構的下一個成員是一個整型值,所以它必須跳過3個位元組到達合適的邊界才能儲存。在整型值之後是最後一個字元。

如果宣告瞭相同型別的第二個變數,它的起始儲存位置也必須滿足4這個邊界,所以第一個結構體的後面還要再跳過3個位元組才能儲存第二個結構體。因此,每個結構體將佔據12個位元組的記憶體空間,但實際只使用了其中的6個,這個利用率可不是很出色。
這裡寫圖片描述

我們可以在宣告中對結構體的成員列表重新排列,讓那些對邊界要求最嚴格的成員首先出現,對邊界要求最弱的成員最後出現。這種做法可以最大限度地減少因邊界對齊而帶來的空間損失。例如:

struct ALIGN2 {
        int b;
        char a;
        char c;
};

它所包含的成員和前面那個結構體一樣,但它只佔用8個位元組的空間,節省了三分之一。兩個字元可以緊挨著儲存,所以只有結構體最後面需要跳過的兩個位元組被浪費。
這裡寫圖片描述

有時,我們有充分的理由,決定不對結構體的成員進行重新排列以減少因對齊帶來的空間損失,例如:我們可能想把相關的結構體成員儲存在一起,提高程式的可維護性和可讀性。但是,如果不存在這樣的理由,結構體的成員應該根據它們的邊界需要進行重新排列,減少因邊界對齊而造成的記憶體損失。

當程式將建立幾百個甚至幾千個結構體時,減少記憶體浪費的要求就比程式的可讀性更為急迫。在這種情況下,在宣告中增加註釋可能避免可讀性方面的損失。

sizeof操作符可以得出一個結構體的整體長度,包括因邊界對齊而跳過的那些位元組。如果你必須確定結構體中某個成員的實際位置,應該考慮邊界對齊因素,可以使用offsetof巨集:

#include <stddef.h>

// 得到指定成員開始儲存的位置距離結構體開始儲存的位置偏移了幾個位元組
size_t offsetof( structName, memberName );

例如,拿之前宣告的結構體舉例:
offsetof( struct ALIGN, b )的返回值為4

結構體資料成員對齊的意義

許多實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的起始地址的值是某個數k的倍數,這就是所謂的記憶體對齊,而這個k則被稱為該資料型別的對齊模數(alignment modulus)。這種強制的要求一來簡化了處理器與記憶體之間傳輸系統的設計,二來可以提升讀取資料的速度。
比如這麼一種處理器,它每次讀寫記憶體的時候都從某個8的倍數的地址開始,一次讀出或寫入8個位元組的資料,假如軟體能保證double型別的資料都從8倍數地址開始,那麼讀或寫一個double型別資料就只需要一次記憶體操作。否則,我們就可能需要兩次記憶體操作才能完成這個動作,因為資料或許恰好橫跨在兩個符合對齊要求的8位元組記憶體塊上。


參考資料:
1.Kenneth A. Reek.C和指標.北京:人民郵電出版社,2008
2.http://www.cnblogs.com/motadou/archive/2009/01/17/1558438.html

相關文章