1. 什么是內存對齊
看下面的小程序,理論上,int占4 byte,char占一個1 byte,那么將它們放到一個結構體中應該占4+1=5byte,但是實際上,通過運行程序得到的結果是8 byte,這就是內存對齊所導致的。
struct Struct {
int a; // 4
char b; // 1
}struct4;
NSLog(@"%lu",sizeof(struct4)); // 輸出為 8
計算機中內存空間都是按照 byte 劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但是實際的計算機系統(tǒng)對基本類型數(shù)據(jù)在內存中存放的位置有限制,它們會要求這些數(shù)據(jù)的首地址的值是某個數(shù)k(通常它為4或8)的倍數(shù),這就是所謂的內存對齊。
2. 為什么要內存對齊
平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
計算機的處理器是以一定大小的塊來進行讀取的,這作為我們的前提條件。
對齊跟數(shù)據(jù)在內存中的位置有關。如果一個變量的內存地址剛好位于它本身長度的整數(shù)倍,他就被稱做自然對齊。例如一個整型變量(占4字節(jié))的地址為0x00000008,那它就是自然對齊的。
現(xiàn)在假設一個整型變量(4字節(jié))不是自然對齊的,它的起始地址落在0x00000002(圖中藍色區(qū)域),處理器想要訪問它的值,按照4字節(jié)的塊進行讀取,從圖中的0x00起讀,讀取4字節(jié)大小,讀到0x03
這樣的一次讀取之后,我們并不能取到我們要訪問的整型數(shù)據(jù),緊接著處理器會繼續(xù)再往下讀,偏移4個字節(jié),從0x04開始,讀到0x07
到這一步,處理器才能讀取到了我們需要訪問的內存數(shù)據(jù),當然這中間還存在剔除與合并的過程。在上面的例子中,要讀取兩次才能獲取到我們想要的數(shù)據(jù)。
那么如果是內存對齊的呢?
由上圖可知,只要讀取一次就能獲取到相應的數(shù)據(jù)。
因此可以得出,內存是否對齊,會影響到數(shù)據(jù)的讀取效率。另外不同的內存存取粒度對同一任務也有不同影響,這里我們不過多的討論。
3.內存對齊原則
- 數(shù)據(jù)成員對?規(guī)則:結構(
struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方(即首地址的位置),以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組,結構體等)的整數(shù)倍開始(比如int為4字節(jié),則要從4的整數(shù)倍地址開始存儲。- 結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從其內部最大元素大小的整數(shù)倍地址開始存儲.(
struct a里存有struct b,b里有char、int、double等元素,那b應該從8的整數(shù)倍開始存儲.)- 收尾工作:結構體的總大小,也就是
sizeof的結果,必須是其內部最大成員的整數(shù)倍,不足的要補?。
按我自己的理解來看:
- 對于規(guī)則1 每個數(shù)據(jù)成員的起始位置,都是自身大小的整數(shù)倍。
- 對于規(guī)則2 對于結構體做為成員變量,起始位置要根據(jù)自身成員變量最大的元素來確定。
下面我們來看一個簡單的例子:
struct TestStruct {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
}struct1;
double的大小為8個字節(jié),按照規(guī)則1, a成員變量將會占據(jù)前首地址開始的 8個字節(jié)。也就是0x00到0x07的地址。
char的大小為1,按照規(guī)則1,內存中的第9為是1的倍數(shù),占據(jù)一個字節(jié)0x08,單數(shù)成員變量c,為int類型,大小為4,內存中的第10為并不是4的倍數(shù),所以并不能滿足規(guī)則1。因此,成員變量b要補3個位置,即占據(jù)0x08到0x11的地址單元,成員變量c從0x12到0x15開始占據(jù)4個內存單元。同理,按照規(guī)則1可以得出成員變量d占據(jù)0x16到0x08的內存單元,總共占據(jù)18個字節(jié)。最后,我們進行收尾工作:結構體的總大小,必須是其內部最大成員的整數(shù)倍。內部最大成員為double 8個字節(jié),可以計算出結構的總大小為24。
最后我們用代碼來驗證下:
如果結構體中存在結構體成員變量會怎樣?
struct TestStruct2 {
double a; // 8
int b; // 4
char c; // 1
short d; // 2
int e; // 4
struct TestStruct1 str;
}struct2;
按照規(guī)則1,可以得出前5個變量占據(jù)24個內存單元,對于結構體成員str,其內部最大成員為double 8個字節(jié),而24為8的倍數(shù),符合規(guī)則1, str占據(jù)后面24個字節(jié)單元。此時,結構體總共占據(jù)48個內存單元,按照規(guī)則3 TestStruct2的最大成員是 TestStruct1 str 24個字節(jié),符合規(guī)則。
下面我么用代碼驗證下結果
4. 總結
其實,內存對齊就是定制了一套規(guī)則,以合理的利用內存空間并提高內存訪問效率。 編譯器通過適當增加padding,使每個成員的訪問都在一個指令里完成,而不需要多次訪問再拼接。是一個以空間換時間的過程。