C語言熱門考點結構體與內存對齊詳解

一、引例

到底什麼是結構體內存對齊,我們用一段代碼來介紹一下

struct S1
{
	char c1;//1字節
	int a;//4字節
	char c2;//1字節
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	//這裡打印12
}

先來解釋S1,結構體S1中有2個char類型,1個int類型。那按道理應該是占2*1+4=6個字節啊,為什麼打印的是12呢?到這裡,我們必須要來瞭解一下結構體內存對齊的規則:

1.結構體的第一個成員永遠放在結構體起始位置偏移量為0的位置

對於偏移量你可以這樣理解:數組下標為0的相對它自己偏移量為0,下標為1的相對下標為0的偏移量為1…
舉例說明:

在這裡插入圖片描述

S1第一個成員是c1,它會被放在結構體起始位置偏移量為0的位置,如下圖紅色部分

在這裡插入圖片描述

2.從第二個成員開始,總是放在偏移量為一個對齊數的整數處,對齊數=編譯器默認的對齊數和變量自身大小的較小值

對齊數=min(編譯器默認的對齊數,變量自身大小)
Linux-沒有對齊數,VS下對齊數默認為8

我們仍以S1這個結構體進行舉例,結構體第二個成員是int類型的a,占4個字節,筆者VS環境下默認對齊數是8,取兩者較小值是4,那a應該放到偏移量為4的倍數上

在這裡插入圖片描述

放到4的倍數上也就說可以放在偏移量為4這裡,偏移量為1,2,3的這3個空間就白白被浪費瞭。而a是int型占4個字節,所以會一直占用到偏移量為7的位置。

接下來是結構體的第三個成員,char類型的c2,c2占1個字節,VS環境下默認對齊數是8,取較小值為1,也就是說隻要是1的倍數的偏移量都可以放,我們緊接著放在a後面,也就是偏移量8的位置

在這裡插入圖片描述

那到這裡結構體3個成員都用完瞭啊,隻有8個啊,為什麼打印是12呢?這裡就要涉及結構體內存對齊的第3個規則

3.結構體的總大小必須是各個成員的對齊數中最大的那個對齊數的整數倍

我們由前面講解知道結構體三個成員c1,a,c2對齊數分別為1,4,1這三個中最大對齊數是4,總大小要為4的整數倍,那這時候肯定有小夥伴會問:我們現在不是對齊到8瞭嘛,8不是4的倍數嗎?註意!這裡說的是空間總大小,而8是所謂的偏移量,偏移量是從0開始算的,到8已經有9個空間瞭,所以我們這裡空間要到12,也就是偏移量到11

在這裡插入圖片描述

(後面加上的三個空間用不到,但是由於規定還是算在結構體總空間內)

二、小試牛刀

我們再來看一道類似的題目

代碼如下(示例):

struct S2
{
	char c1;//1字節
	char c2;//1字節
	int a;//4字節
};
int main()
{
	printf("%d\n", sizeof(struct S2));
	//這裡打印8
}

首先第一個結構體成員是char類型的c1,由規則1,它會直接被放在偏移量為0的位置
(圖示灰色部分)

在這裡插入圖片描述

第二個成員是char類型的c2,占1字節,VS下默認對齊數是8,取較小值是1,隻要放在偏移量為1的倍數上即可(任意位置),緊跟著0,放在偏移量為1處(圖示紅色部分)

在這裡插入圖片描述

最後一個成員int類型的a,占4個字節,VS環境下默認對齊數是8,取較小者4,放在偏移量為4的整數倍處,也就是4這裡,然後由於int占4個字節所以一直占用到偏移量7處

在這裡插入圖片描述

再來看看規則3,結構體的總大小必須是各個成員的對齊數中最大的那個對齊數的整數倍,也就是4的倍數,我們現在正好是占8個空間,8正好是4的倍數,所以就不用再往下浪費空間瞭,打印出8

三、嵌套結構體的特殊情況

代碼如下(示例):

struct S3
{
	double d;//double占8字節,默認對齊數8,取較小值,對齊數8
	char c;//對齊數1
	int i;//對齊數4
};
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S4));
}

關於結構體S3我們可以采用和前面S1、S2一樣的方法計算出來是占16個字節空間,我們這裡重點討論S4,對S3有興趣的小夥伴可自行求解。

S4中的第一個成員c1,按規則1直接放在偏移量為0處,第二個成員s3怎麼辦呢?這裡涉及結構體內存對齊的第四個規則:

如果嵌套瞭結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍數處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍

s3這個結構體三個成員最大對齊數是8,也就是要對齊到偏移量為8的倍數處,然後s3是占16個字節,所以一直占到偏移量23處(s3結構體對齊數是本身s3結構體三個成員中最大對齊數)

ps:在VS環境中,嵌套結構體的最大對齊數超過8,仍然用8做最大對齊數(比默認對齊數大瞭,取較小值就取默認對齊數瞭)

在這裡插入圖片描述

S4最後一個成員double類型的d占8字節,默認對齊數8,對齊數取8,然後放在偏移量為對齊數的整數倍處,正好往下放在24處,本身占8字節所以占到31

在這裡插入圖片描述

偏移量0-31共占32字節,S4中的成員c1,s3,d對齊數分別為1,8,8所以最大對齊數是8,32恰是8的倍數,所以這裡不用再浪費空間來滿足 “結構體的總大小必須是各個成員的對齊數中最大的那個對齊數的整數倍”這個規則,結構體總大小就是32

四、關於為什麼存在內存對齊

1.平臺原因(移植原因):

不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺隻能在某些地址處取某些特定的類型的數據,否則拋出硬件異常

2.性能原因:

數據結構(尤其是棧),應盡可能地在自然邊界上對齊。原因在於,為瞭訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅僅需要1次
總體來說:結構體的內存對齊是用空間換時間

總結

本文介紹瞭結構體內存對齊的四大規則,並舉例說明瞭如何進行方法的操作,對於其中特殊的嵌套結構體內存對齊也進行瞭相應講解,希望讀者在學習完本文後能對結構體內存對齊有一個系統的認識。祝讀者學業有成!

更多關於C語言結構體與內存對齊的資料請關註WalkonNet其它相關文章!

推薦閱讀: