内存对齐详解
為什么要進行內存對其?
(1)?性能原因:數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
(2) 平臺原因:不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
(3)?空間原因:沒有進行內存對齊的結構體或類會浪費一定的空間,當創建對象越多時,消耗的空間越多。
?
在 C/C++ 中,結構體/類是一種復合數據類型,其構成元素既可以是基本數據類型(如int、long、float等)的變量,也可以是一些復合數據類型(如數組、結構、聯合等)的數據單元。編譯器為每個成員按其自然邊界(alignment)分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同。
如果一個變量的內存地址正好位于它長度的整數倍,他就被稱做自然對齊。如果在 32 位的機器下,一個int類型的地址為0x00000004,那么它就是自然對齊的。同理,short 類型的地址為0x00000002,那么它就是自然對齊的。char 類型就比較 "隨意" 了,因為它本身長度就是 1 個字節。自然對其的前提下:
char?? 偏移量為sizeof(char) 即 1 的倍數? short??偏移量為sizeof(short) 即 2 的倍數? int?? 偏移量為sizeof(int) 即 4 的倍數? float??偏移量為sizeof(float) 即 4 的倍數? double?偏移量為sizeof(double) 即 8 的倍數??
在設置結構體或類時,不考慮內存對齊問題,會浪費一些空間,例如實驗一:
struct asd1{char a;int b;short c; };//12字節struct asd2{char a;short b;int c; };//8字節上面兩個結構體擁有相同的數據成員 char、short 和 int,但由于各個成員按照它們被聲明的順序在內存中順序存儲,所以不同的聲明順序導致了結構體所占空間的不同。具體如下圖:
? ? ? ? ? ? ?
?
看到上面的第二張圖,有的人可能會有疑問,為什么 short 不是緊挨著 char 呢?其實這個原因在上面已經給出了答案——自然對齊。為此,我們可以創建結構體驗證自然對齊的規則。實驗很簡單,在原本 short 類型變量前后添加 char 類型,看結果是怎樣的。實驗二:
struct asd3{char a;char b;short c;int d; };//8字節struct asd4{char a;short b;char cint d; };//12字節? ? ? ? ? ? ??
?
需要注意的一點
當數據成員中有 double 和 long 時,情況又會有一點變化。還是以上面的結構體 asd1 和 asd2 為基礎,都添加 double 型數據成員。來看看結果是什么,實驗三:
struct asd1{char a;int b;short c;double d; };//24個字節struct asd2{char a;short b;int c;double d; };//16個字節只添加了一個 double,但 struct asd1 的大小從 12 變到了 24。而 struct asd2 的大小從 8 變到了 16。不需要迷惑,因為這和 double 的自然對其有關(需要注意)。原本的 asd1 占 12 個字節大小,但是 double 對齊需要是 8 的倍數,所以在 short 后面又填充了 4 個字節。此時,asd1 的占 16 個字節,再加上 double 的 8 個字節就成了 24 個字節。而 asd2 沒有這個問題,它原本占 8 個字節。因為正好能對齊,所以添加 double 后占 16 個字節。具體情況如下圖所示:?
? ? ? ? ? ? ? ??? ? ?
?
?
指定對齊值
在缺省情況下,C 編譯器為每一個變量或是數據單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變缺省的對界條件:
使用偽指令 #pragma pack (n),C 編譯器將按照 n 個字節對齊。
使用偽指令 #pragma pack (),取消自定義字節對齊方式。
實驗四:
#pragma pack(4) struct asd5{char a;int b;short c;float d;char e; };//20 #pragma pack()#pragma pack(1) struct asd6{char a;int b;short c;float d;char e; };//12 #pragma pack()使用 #pragma pack (value)?指令將結構體按相應的值進行對齊。兩個結構體包含同樣的成員,但是卻相差 8 個字節。難道我們只需要通過簡單的指令就能完成內存對齊的工作嗎?其實不是的。上面的對齊結果如下:
以 32 位機器為例,CPU 取的字長是 32 位。所以上面的對齊結果會這樣帶來的問題是:訪問未對齊的內存,處理器需要作兩次內存訪問。如果我要獲取 int 和 float 的數據,處理器需要訪問兩次內存,一次獲取 "前一部分" 的值,一次獲取 "后一部分" 的值。這樣做雖然減少了空間,但是增加訪問時間的消耗。其實最理想的對齊結果應該是:
ps.使用 #pragma pack(4) 可以讓前面的實驗三中的 asd1 少占用 4 字節。
?
?
對齊原則
1. 數據類型自身的對齊值:對于 char 型數據,其自身對齊值為1,對于 short 型為2,對于 int,float,double 類型,其自身對齊值為 4,單位字節。(上面實驗二已經驗證過了)
2.?結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
3. 指定對齊值:#pragma pack (value) 時的指定對齊值 value。
4.?數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
總結
- 上一篇: mmap 内存映射详解
- 下一篇: C++实现字符串分割函数split()