內存地址對齊,是一種在計算機內存中排列數據、訪問數據的一種方式,包含了兩種相互獨立又相互關聯的部分:基本數據對齊和結構體數據對齊。當今的計算機在計算機內存中讀寫數據時都是按字(word)大小塊來進行操做的(在32位系統中,數據總線寬度爲32,每次能讀取4字節,地址總線寬度爲32,所以最大的尋址空間爲2^32=4GB,可是最低2位A[0],A[1]是不用於尋址,A[2-31]才能存儲器相連,所以只能訪問4的倍數地址空間,可是總的尋址空間仍是2^30*字長=4GB,所以在內存中全部存放的基本類型數據的首地址的最低兩位都是0,除結構體中的成員變量)。基本類型數據對齊就是數據在內存中的偏移地址必須等於一個字的倍數,按這種存儲數據的方式,能夠提高系統在讀取數據時的性能。爲了對齊數據,可能必須在上一個數據結束和下一個數據開始的地方插入一些沒有用處字節,這就是結構體數據對齊。數組
舉個例子,假設計算機的字大小爲4個字節,所以變量在內存中的首地址都是知足4地址對齊,CPU只能對4的倍數的地址進行讀取,而每次能讀取4個字節大小的數據。假設有一個整型的數據a的首地址不是4的倍數(以下圖所示),不妨設爲0X00FFFFF3,則該整型數據存儲在地址範圍爲0X00FFFFF3~0X00FFFFF6的存儲空間中,而CPU每次只能對4的倍數內存地址進行讀取,所以想讀取a的數據,CPU要分別在0X00FFFFF0和0X00FFFFF4進行兩次內存讀取,並且還要對兩次讀取的數據進行處理才能獲得a的數據,而一個程序的瓶頸每每不是CPU的速度,而是取決於內存的帶寬,由於CPU得處理速度要遠大於從內存中讀取數據的速度,所以減小對內存空間的訪問是提升程序性能的關鍵。從上例能夠看出,採起內存地址對齊策略是提升程序性能的關鍵。性能
舉例:測試
首先咱們先看看下面的C語言的結構體:優化
typedef struct MemAlign { int a; char b[3]; int c; }MemAlign;
以上這個結構體佔用內存多少空間呢?也許你會說,這個簡單,計算每一個類型的大小,將它們相加就好了,以32爲平臺爲例,int類型佔4字節,char佔用1字節,因此:4 + 3 + 4 = 11,那麼這個結構體一共佔用11字節空間。好吧,那麼咱們就用實踐來證實是否正確,咱們用sizeof運算符來求出這個結構體佔用內存空間大小,sizeof(MemAlign),出乎意料的是,結果竟然爲12?看來咱們錯了?固然不是,而是這個結構體被優化了,這個優化有個另一個名字叫「對齊」,那麼這個對齊到底作了什麼樣的優化呢,聽我慢慢解釋,再解釋以前咱們先看一個圖,圖以下:ui
相信學過彙編的朋友都很熟悉這張圖,這張圖就是CPU與內存如何進行數據交換的模型,其中,左邊藍色的方框是CPU,右邊綠色的方框是內存,內存上面的0~3是內存地址。這裏咱們這張圖是以32位CPU做爲表明,咱們都知道,32位CPU是以雙字(DWORD)爲單位進行數據傳輸的,也正由於這點,形成了另一個問題,那麼這個問題是什麼呢?這個問題就是,既然32位CPU以雙字進行數據傳輸,那麼,若是咱們的數據只有8位或16位數據的時候,是否是CPU就按照咱們數據的位數來進行數據傳輸呢?其答案是否認的,若是這樣會使得CPU硬件變的更復雜,因此32位CPU傳輸數據不管是8位或16位都是以雙字進行數據傳輸。那麼也罷,8位或16位同樣能夠傳輸,可是,事情並不是像咱們想象的那麼簡單,好比,一個int類型4字節的數據若是放在上圖內存地址1開始的位置,那麼這個數據佔用的內存地址爲1~4,那麼這個數據就被分爲了2個部分,一個部分在地址0~3中,另一部分在地址4~7中,又因爲32位CPU以雙字進行傳輸,因此,CPU會分2次進行讀取,一次先讀取地址0~3中內容,再一次讀取地址4~7中數據,最後CPU提取並組合出正確的int類型數據,捨棄掉無關數據。那麼反過來,若是咱們把這個int類型4字節的數據放在上圖從地址0開始的位置會怎樣呢?讀到這裏,也許你明白了,CPU只要進行一次讀取就能夠獲得這個int類型數據了。沒錯,就是這樣,此次CPU只用了一個週期就獲得了數據,因而可知,對內存數據的擺放是多麼重要啊,擺放正確位置能夠減小CPU的使用資源。spa
那麼,內存對齊有哪些原則呢?我總結了一下大體分爲三條:
第一條:第一個成員的首地址爲0
第二條:每一個成員的首地址是自身大小的整數倍
第二條補充:以4字節對齊爲例,若是自身大小大於4字節,都以4字節整數倍爲基準對齊。
第三條:最後以結構整體對齊。
第三條補充:以4字節對齊爲例,取結構體中最大成員類型倍數,若是超過4字節,都以4字節整數倍爲基準對齊。(其中這一條還有個名字叫:「補齊」,補齊的目的就是多個結構變量挨着擺放的時候也知足對齊的要求。).net
上述的三原則聽起來仍是比較抽象,那麼接下來咱們經過一個例子來加深對內存對齊概念的理解,下面是一個結構體,咱們動手算出下面結構體一共佔用多少內存?假設咱們以32位平臺而且以4字節對齊方式:設計
#pragma pack(4) typedef struct MemAlign { char a[18]; double b; char c; int d; short e; }MemAlign;
下圖爲對齊後結構以下:指針
咱們就以這個圖來說解是如何對齊的:
第一個成員(char a[18]):首先,假設咱們把它放到內存開始地址爲0的位置,因爲第一個成員佔18個字節,因此第一個成員佔用內存地址範圍爲0~18。
第二個成員(double b):因爲double類型佔8字節,又由於8字節大於4字節,因此就以4字節對齊爲基準。因爲第一個成員結束地址爲18,那麼地址18並非4的整數倍,咱們須要再加2個字節,也就是從地址20開始擺放第二個成員。
第三個成員(char c):因爲char類型佔1字節,任意地址是1字節的整數倍,因此咱們就直接將其擺放到緊接第二個成員以後便可。
第四個成員(int d):因爲int類型佔4字節,可是地址29並非4的整數倍,因此咱們須要再加3個字節,也就是從地址32開始擺放這個成員。
第五個成員(short e):因爲short類型佔2字節,地址36正好是2的整數倍,這樣咱們就能夠直接擺放,無需填充字節,緊跟其後便可。
這樣咱們內存對齊就完成了。可是離成功還差那麼一步,那是什麼呢?對,是對整個結構體補齊,接下來咱們就補齊整個結構體。那麼,先讓咱們回顧一下補齊的原則:「以4字節對齊爲例,取結構體中最大成員類型倍數,若是超過4字節,都以4字節整數倍爲基準對齊。」在這個結構體中最大類型爲double類型(佔8字節),又因爲8字節大於4字 節,因此咱們仍是以4字節補齊爲基準,整個結構體結束地址爲38,而地址38並非4的整數倍,因此咱們還須要加額外2個字節來填充結構體,以下圖紅色的就是補齊出來的空間:code
到此爲止,咱們內存對齊與補齊就完畢了!接下來咱們用實驗來證實真理,程序以下:
#include <stdio.h> #include <memory.h> // 因爲VS2010默認是8字節對齊,咱們 // 經過預編譯來通知編譯器咱們以4字節對齊 #pragma pack(4) // 用於測試的結構體 typedef struct MemAlign { char a[18]; // 18 bytes double b; // 08 bytes char c; // 01 bytes int d; // 04 bytes short e; // 02 bytes }MemAlign; int main() { // 定義一個結構體變量 MemAlign m; // 定義個以指向結構體指針 MemAlign *p = &m; // 依次對各個成員進行填充,這樣咱們能夠 // 動態觀察內存變化狀況 memset( &m.a, 0x11, sizeof(m.a) ); memset( &m.b, 0x22, sizeof(m.b) ); memset( &m.c, 0x33, sizeof(m.c) ); memset( &m.d, 0x44, sizeof(m.d) ); memset( &m.e, 0x55, sizeof(m.e) ); // 因爲有補齊緣由,因此咱們須要對整個 // 結構體進行填充,補齊對齊剩下的字節 // 以便咱們能夠觀察到變化 memset( &m, 0x66, sizeof(m) ); // 輸出結構體大小 printf( "sizeof(MemAlign) = %d", sizeof(m) ); }
程序運行過程當中,查看內存以下:
其中,各類顏色帶下劃線的表明各個成員變量,藍色方框的表明爲內存對齊時候填補的多餘字節,因爲這裏看不到補齊效果,咱們接下來看下圖,下圖籃框包圍的字節就是與上圖的交集之外的部分就是補齊所填充的字節。
在最後,我在談一談關於補齊的做用,補齊其實就是爲了讓這個結構體定義的數組變量時候,數組內部,也一樣知足內存對齊的要求,爲了更好的理解這點,我作了一個跟本例子相對照的圖:
參考連接:https://blog.csdn.net/donkeylong/article/details/4909720