您當前的位置:首頁 > 體育

一文輕鬆理解記憶體對齊

作者:由 C語言與CPP程式設計 發表于 體育時間:2020-05-12

什麼是記憶體對齊

元素是按照定義順序一個一個放到記憶體中去的,但並不是緊密排列的。從結構體儲存的首地址開始,每個元素放置到記憶體中時,它都會認為記憶體是按照自己的大小(通常它為4或8)來劃分的,因此元素放置的位置一定會在自己寬度的整數倍上開始,這就是所謂的記憶體對齊。

編譯器為程式中的每個“資料單元”安排在適當的位置上。C語言允許你干預“記憶體對齊”。如果你想了解更加底層的秘密,“記憶體對齊”對你就不應該再模糊了。

以一個例子開始瞭解

理論上,int佔4byte,char佔一個byte,那麼將它們放到一個結構體中應該佔4+1=5byte;但是實際上,透過執行程式得到的結果是8 byte,這就是記憶體對齊所導致的。

#include

struct{

int x;

char y;

}Test;

int main()

{

printf(“%d\n”,sizeof(Test)); // 輸出8不是5

return 0;

}

為什麼要記憶體對齊

平臺原因(移植原因)

:不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。

效能原因

:資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問。

假如沒有記憶體對齊機制,資料可以任意存放,現在一個int變數存放在從地址1開始的聯絡四個位元組地址中,該處理器去取資料時,要先從0地址開始讀取第一個4位元組塊,剔除不想要的位元組(0地址),然後從地址4開始讀取下一個4位元組塊,同樣剔除不要的資料(5,6,7地址),最後留下的兩塊資料合併放入暫存器。這需要做很多工作。

現在有了記憶體對齊的,int型別資料只能存放在按照對齊規則的記憶體中,比如說0地址開始的記憶體。那麼現在該處理器在取資料時一次性就能將資料讀出來了,而且不需要做額外的操作,提高了效率。

記憶體對齊規則

基本型別

的對齊值就是其sizeof值;

資料成員對齊規則

:結構(struct)(或聯合(union))的資料成員,第一個資料成員放在offset為0的地方,以後每個資料成員的對齊按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行;

結構(或聯合)的整體對齊規則

:在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行;

//2020。05。12 公眾號:C語言與CPP程式設計

#include

struct

{

int i;

char c1;

char c2;

}Test1;

struct{

char c1;

int i;

char c2;

}Test2;

struct{

char c1;

char c2;

int i;

}Test3;

int main()

{

printf(“%d\n”,sizeof(Test1)); // 輸出8

printf(“%d\n”,sizeof(Test2)); // 輸出12

printf(“%d\n”,sizeof(Test3)); // 輸出8

return 0;

}

預設#pragma pack(4),且結構體中最長的資料型別為4個位元組,所以有效對齊單位為4位元組,下面根據上面所說的規則以第二個結構體來分析其記憶體佈局: 首先使用規則1,對成員變數進行對齊:

sizeof(c1) = 1 <= 4(有效對齊位),按照1位元組對齊,佔用第0單元;

sizeof(i) = 4 <= 4(有效對齊位),相對於結構體首地址的偏移要為4的倍數,佔用第4,5,6,7單元;

sizeof(c2) = 1 <= 4(有效對齊位),相對於結構體首地址的偏移要為1的倍數,佔用第8單元;

然後使用規則2,對結構體整體進行對齊:

第二個結構體中變數i佔用記憶體最大佔4位元組,而有效對齊單位也為4位元組,兩者較小值就是4位元組。因此整體也是按照4位元組對齊。由規則1得到s2佔9個位元組,此處再按照規則2進行整體的4位元組對齊,所以整個結構體佔用12個位元組。

根據上面的分析,不難得出上面例子三個結構體的記憶體佈局如下:

一文輕鬆理解記憶體對齊

更改C編譯器的預設位元組對齊方式:

在預設情況下,C編譯器為每一個變數或是資料單元按其自然對界條件分配空間。一般地,可以透過下面的方法來改變預設的對界條件:

使用偽指令#pragma pack (n),C編譯器將按照n個位元組對齊。

使用偽指令#pragma pack (),取消自定義位元組對齊方式。

另外,還有如下的一種方式:

__attribute((aligned (n))),讓所作用的結構成員對齊在n位元組自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊。

attribute((packed)),取消結構在編譯過程中的最佳化對齊,按照實際佔用位元組數進行對齊。

不同平臺上編譯器的 pragma pack 預設值不同。而我們可以透過預編譯命令#pragma pack(n), n= 1,2,4,8,16來改變對齊係數。

例如,對於上個例子的三個結構體,如果前面加上#pragma pack(1),那麼此時有效對齊值為1位元組,此時根據對齊規則,不難看出成員是連續存放的,三個結構體的大小都是6位元組。

一文輕鬆理解記憶體對齊

如果前面加上#pragma pack(2),有效對齊值為2位元組,此時根據對齊規則,三個結構體的大小應為6,8,6。記憶體分佈圖如下:

一文輕鬆理解記憶體對齊

例子

請寫出以下程式碼的輸出結果:

#include

struct S1

{

int i:8;

char j:4;

int a:4;

double b;

};

struct S2

{

int i:8;

char j:4;

double b;

int a:4;

};

struct S3

{

int i;

char j;

double b;

int a;

};

int main()

{

printf(“%d\n”,sizeof(S1)); // 輸出8

printf(“%d\n”,sizeof(S1); // 輸出12

printf(“%d\n”,sizeof(Test3)); // 輸出8

return 0;

}

分析問題

在儲存某些資料時,其實際需求的資料長度可能要小於一個位元組,只佔一位或幾位。為了節省空間,處理方便,在C中引入了另一種結構,稱為“位域”或“位段”。

所謂“位域”,就是把一個位元組中的“位”按照實際的需求分成不同的區域,表明每個區域位數、區域的域名,並允許程式按照域名進行操作。如此就可以把不同的物件用一個位元組來表示。

位域的定義與結構定義相仿,其形式為:

struct 位域的結構體名

{

//位域列表

}

位域列表的形式為:【型別說明符】 【位域名】:【位域的長度】例如:

struct ab

{

int a:8;

int b:2;

int c:6;

}

對於位域的定義,有以下幾點說明:

(1)一個位域必須儲存在同一個位元組中,不能跨兩個位元組。如一個位元組所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。

例如:

struct wy

{

unsigned a:6;

unsigned 0; //空域

unsigned b:4; //從一單元開始存放

unsigned c:4;

}

在這個位域定義中,a佔第一位元組的6位,後2位填0表示不使用,b從第二位元組開始,佔用4位,c佔用4位。記憶體結構如下圖所示。

一文輕鬆理解記憶體對齊

(2)由於位域不允許跨兩個位元組,因此位域的長度不能大於一個位元組的長度,也就是不能超過8位二進位。

(3)位域可以無位域名,這時它只用來填充或調整位置。無名的位域是不能使用的。

例如:

struct wk

{

int a:1;

int :2; //不能使用

int b:3;

int c:2;

}

一文輕鬆理解記憶體對齊

從以上述分析可以看出,位域可以看做是一種結構型別,其特點是成員均按二進位分配。

根據以上分析可知,在s1中i在相對0的位置,佔8位即第1個位元組。j就在相對第2個位元組的位置。由於一個位置的位元組數是4位的倍數,因此不用對齊,可以就放在那裡。a要放在4位倍數關係的位置上,因此不用對齊,就放在那裡。

目前總共是16位,2位元組,由於double是8位元組的,因此要在距相對0位置為8個位元組的位置處放下。所以從16位開始到8個位元組之間的位置被忽略,直接放在相對第8位元組的位置,因此,s1總共佔16位元組。儲存結構如下圖所示。

一文輕鬆理解記憶體對齊

在s2中,每個資料都要對照結構體內最大資料的最小公倍數補齊。如i,j共12位,小於double的8個位元組需按8位元組補齊,a位也要按8位元組補齊,共24個位元組,儲存結構如下圖所示。

一文輕鬆理解記憶體對齊

在s3中,i是int型資料(按32位機分析)佔4個位元組,j是char型資料佔一個位元組,a是int型資料佔4個位元組,b是double型資料佔8個位元組。 在此b是最大的資料型別,因此i、j、a都要向b的double型對齊,即i、j、a的資料長度要向b對齊為8個位元組,四個資料共佔據32個位元組。s3的儲存結構如下圖所示。

一文輕鬆理解記憶體對齊

答案

sizeof(S1)=16

sizeof(S2)=24

sizeof(S3)=32

說明

:結構體作為一種複合資料型別,其構成元素既可以是基本資料型別的變數,也可以是一些複合型型別資料。對此,編譯器會自動進行成員變數的對齊以提高運算效率。預設情況下,按自然對齊條件分配空間。各個成員按照它們被宣告的順序在記憶體中順序儲存,第一個成員的地址和整個結構的地址相同,向結構體成員中size最大的成員對齊。

許多實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的首地址的值是某個數k(通常它為4或8)的倍數,而這個k則被稱為該資料型別的對齊模數。

標簽: 對齊  位元組  int  記憶體  位域