一、什么是內存對齊,為什么要內存對齊
-
- 現在計算機內存空間都是按照byte字節划分的,理論上講對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址上訪問,這就需要各種數據類型按照一定的規則在空間上排列,而不是一個接一個的排放,這就是內存對齊。
- cpu對內存的讀取不是連續的而是分塊讀取的,塊的大小只能是2i個字節數,從cpu的讀取性能和效率來考慮,若讀取的數據未對齊,則需要兩次總線周期來訪問內存,因而效率會大打折扣
- 另外某些固定的硬件平台只能從規定的相對地址處讀取特定類型的數據,否則會產生硬件異常。
- 如果不按照適合平台要求對數據存放進行對齊,會存在效率上的損失。比如有些平台每次讀都是從偶地址開始,如果一個int型(32位系統)存放在偶地址開始的地方,那么一個周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要兩個讀周期,並對兩次讀出結果的高低字節進行拼湊才能得到該32bit數據。顯然在讀取效率上下降很多。
-
-
- 上圖說明的是cpu如何與內存進行數據交換的模型,左邊是cpu,右邊是內存空間,內存上邊的0~3是內存地址。這張圖以32位cpu作為代表。
- 32位cpu是以雙字為單位進行數據傳輸的,正因為這個原因,如果我們的數據只有8位或16位,cpu是不是就會以我們數據的位數進行傳輸呢,答案是否定的,這樣會使的cpu硬件變得復雜,所以32位cpu傳輸數據無論是8位或是16位都是以雙字進行傳輸的。
- 比如一個int類型的4字節數據如果放在上圖內存地址1開始的地方,那么這個數據占用的內存地址為1~4,那么這個數據就被分成了2部分,一部分在0~3地址上,一部分在4~7地址上。又因為32位cpu以雙字傳輸,所以cpu會分兩次進行讀取,一次先讀取0~3地址上的內容,再一次讀取4~7地址上的數據。最后cpu提取並組合出正確的int類型數據。舍棄掉無關數據。
- 如果把這個int類型數據存放在從地址0開始的地方,cpu只需要一次讀取就可以得到這個int數據類型了。這次cpu只用了一個周期就得到了數據,可見內存擺放很重要,合理的內存對齊可以減少cpu的使用資源
二、內存對齊規則
- 在不用#pagrama pack()包裹的情況下,結構體或聯合體按照編譯器默認的對齊方式有以下三個對其原則:
- 數據成員對齊原則:結構(struct或union)的數據成員,第一個數據成員存放在offset為0的地方,以后每個數據成員存儲的起始位置都要從該成員占用內存大小的整數倍開始
- 結構體作為成員的原則:如果一個結構中有某些結構體成員,則結構的成員要從其內部最大元素大小的整數倍地址開始存儲。(struct a里有struct b,b里有char,int,double等元素,那b應該從8的整數倍開始)
- 結構(或聯合)的整體對齊原則:在數據成員各自對齊后,結構(或聯合)本身也要進行對齊,即以結構體內部占用內存空間最大的數據類型進行對齊。(等同於sizeof該結構體的結果必須是其內部最大成員占用內存的整數倍)
-
1 struct mystruct 2 { 3 char a;//偏移量為0;a占用一個字節 4 double b;//下一個可用地址偏移量為1,不是sizeof(double)=8的整數倍,需要補7個字節 5 int c;//下一個可用地址偏移量為1+7+8=16,是sizeof(int)=4的整數倍,滿足Int的對齊方式 6 }//所有成員變量都分配了空間,空間大小=1+7+8+4=20,不是最大空間類型double的整數倍,所以需要填充4個字節以滿足結構體大小為sizeof(double)=8的整數倍 7 sizeof(mystruct)=24;
-
對於結構體整體對齊:
-
-
三、#pragma pack()自定義數據對齊規則
- #pragma pack(n):每個特定平台上的編譯器都有自己默認的對齊系數,程序員可以通過預編譯指令#pragma pack(n),n=1,2,4,8,16來改變這一系數
- 數據成員對齊規則:結構或聯合的數據成員,第一個數據成員在offset為0的地方,以后每個數據成員的對齊方式都按照#pragma pack指定的數值這個數據成員自身占用內存中比較小的那個進行。
- 結構或聯合整體對其原則:在數據成員完成自身對齊后,結構或聯合本身也要進行對齊,對齊按照#pragma pack(n)指定的數值和結構或聯合最大數據成員占用內存中比較小的那個進行。
-
1 # pragma pack (4) 2 struct node 3 { 4 int e; 5 char f; 6 short int a; 7 char b; 8 } 9 struct node n; 10 printf("%d",sizeof(n)); 11 12 32位系統結果為:12 13 4+1+1+2+1+3=12包括數據成員對齊與結構整體對齊;
-
- #pragma pack(push),#pragma pack(pop),#pragmapack()
- d當有些時候想用4字節對齊,有些時候又想用1字節或8字節對齊時,便用到了push和pop
-
#pragma pack()能夠取消自定義的對齊方式,恢復為默認的對齊方式
-
#pragma pack(push):壓棧,編譯器編譯到此處時將保存對齊狀態(即push指令之前的對齊狀態)
#pragma pack(pop):出棧,編譯器編譯到此處將恢復push指令前保存的對齊狀態,在使用#pragma pack(pop)之前需要使用#pragma pack(push)指令
push和pop指令是一對應該同時出現的名詞,只有pop沒有push不起作用,只有push沒有pop則可以保存之前的對齊狀態,但也是沒必要了
- 當我們想讓一個結構體按照4字節對齊時,可以使用#pragma pack(4),最后又想使用 默認對齊方式時,可以使用#pragma pack();
- 也可以使用:
-
1 #pragma pack(push) 2 #pragma pack(4) 3 struct...... 4 #pragma pack(pop) 5 這樣在push和pop之間的結構體就可以按照pack指定的字節對齊了,而之后的結構體則按照#pragma pack (push)之前的對齊方式對齊
- 需要注意的是:#pragma pack()是取消自定義對齊方式,恢復默認方式,而push之后pop是返回到push指令之前的對齊方式
-
-
- 舉個例子:
-
1 #include <stdio.h> 2 #pragma pack(2) 3 #pragma pack(push) 4 #pragma pack(4) 5 struct CC { 6 double d; 7 char b; 8 int a; 9 short c; 10 }; 11 12 #pragma pack(1) 13 struct BB{ 14 double d; 15 char b; 16 int a; 17 short c; 18 }; 19 #pragma pack(pop) 20 struct AA{ 21 double d; 22 char b; 23 int a; 24 short c; 25 }; 26 int main(void) 27 { 28 29 printf("%u\n%u\n%u\n",sizeof(struct CC),sizeof(struct BB),sizeof(struct AA)); 30 return 0; 31 } 32 33 運行結果為:20,15,16 34 先按照2字節對齊,然后push保存2字節,然后強制4字節對齊,然后打印cc為20字節,然后強制1字節對齊,打印bb為15字節,然后pop,pop之后會
讓編譯器回到Push之前的對齊方式(這里是2字節對齊)打印AA(按照2字節對齊)16字節
-
- #pragma pack(push,1) 作用:將原來的對齊方式設置為壓棧,並設置新的對齊方式為1個字節對齊方式。
- 舉個例子:

