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

舉個例子

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

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

發佈了44 篇原創文章 · 獲贊 34 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章