結構體內存對齊良心講解

本文將介紹以下內容:

一、什麼是內存對齊?

二、結構體大小如何計算?

三、爲什麼存在內存對齊?

四、如何修改默認對齊數?

五、筆試題練習


正文

一、什麼是內存對齊?

  首先,內存對齊是屬於編譯器的“管轄”範圍,編譯器爲程序中的每個數據單元安排在適當的位置上。但是在 c 語言中我們是被允許干預內存對齊的,也就是說我們可以一定程度上的控制如何內存對齊。
內存對齊是爲了硬件計算起來效率更高而設計的
  我們通過一些例子來理解下什麼是內存對齊:(以下程序皆在vs2013下運行)
例1:

typedef struct s1
{
	char a;
	int b;
	char c;
} s1;
printf("%d\n", sizeof(s1));

運行結果是12。

typedef struct s1
{
	char a;
	char b;
	char c;
} s1;
printf("%d\n", sizeof(s1));

運行結果是3。

typedef struct s1
{
	char a;
	short b;
	char c;
} s1;
printf("%d\n", sizeof(s1));

運行結果是6。

typedef struct s1
{
	char a;
	double b;
	char c;
} s1;
printf("%d\n", sizeof(s1));

運行結果是24。


從例1直觀的來看,感覺好像是低字節的元素在向高字節對齊。我們繼續往下看!!!


例2:

typedef struct s2
{
	char a;
	char b;
	int c;
} s2;
printf("%d\n", sizeof(s2));

運行結果是8。

例3:

typedef struct s3
{
	double d;
	char c;
	int i;
} s3;
printf("%d\n", sizeof(s3));

運行結果是16。


例 2 應該是將兩個 char 湊成一個4字節的 int,所以計算出來是8。
例 3 應該是將 char 和 int 湊成了一個 8字節的double,所以計算出來是16。
這看起來也是往低字節往高字節的元素對齊。那麼看下一個例子!!


例4:

	typedef struct s3
	{
		double d;
		char c;
		int i;
	} s3;
	
	typedef struct s4
	{
		char c1;
		struct s3 s3;
		double d;
	} s4;
	printf("%d\n", sizeof(s4));

運行結果是32。


注意:這裏結果發生了變化,如果按照我們上面的推論,那麼這個計算出來應該是往 s3 這個結構體上對齊,s3 是16個字節,那麼按理應該是 48 纔對,看起來這並不是無休止的往最大的字節元素對齊。那麼我們就來看看結構體的大小到底應該如何計算?


二、結構體大小如何計算?

  我們先來看看結構體的對齊規則:

  1. 第一個成員在與結構體變量偏移爲 0 的地址處。
  2. 其他成員變量要對齊到對齊數的整數倍
      對齊數 = 編譯器默認的一個對齊數 與 該成員中最大的變量 兩者之間較小的那個
      提一下:vs中默認的對齊數是 8
          Linux下默認的對齊數是 4
  3. 結構體總大小爲最大對齊數(每一個成員變量都有一個對齊數)的整數倍。
  4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。

看了這些規則,我們來一條條分析:
  第一條很好理解,簡單來說,結構體第一個成員的地址和結構體的地址是一樣的。但是意義不一樣,第一個成員的地址是給結構體的地址加上一個 0 得到的就是第一個成員的地址。
  主要是第二條看起來比較繞口,但是仔細分析也是很好理解,這時候可以看我們的例子,在例2中,結構體中最大的成員是 int ,而vs默認的對齊數是8,我們選兩者中較小的一個作爲對齊數,那麼就是取 4 作爲對齊數。
  再看例 3 中,結構體成員最大的是 double 有 8 個字節,而vs的默認對齊數也是 8 ,所以取 8 作爲對齊數。
  再看例 4 中,結構體成員中最大的是結構體 s3 ,是 16 個字節,而vs默認對齊數是8,取兩者較小的作爲對齊數,所以就取的是 8 作爲對齊數,s3 的大小還是 16 個字節,其他成員變量向 8 對齊。

三、爲什麼存在內存對齊?

  大部分的參考資料都是這樣解釋的:
1、平臺原因(移值原因):
  不是所有的硬件平臺都能訪問任意地址上的任意數據的,某些硬件平臺只能在某些地址處取特定類型的數據,否則拋出硬件異常。
2、性能原因:
  數據結構(尤其是棧)應該儘可能地在自然邊界上對齊。
  原因在於,爲了訪問未對齊的內存,處理器需要作出兩次內存訪問,而對齊的內存僅需要一次訪問,也就是下面的解釋。
  cpu在取數據的時候一般都是 4 個字節或 8 個字節的取,如果沒有內存對齊這回事那麼取數據就會出現下面的情況
在這裏插入圖片描述
沒有內存對齊的話,取出來的數據就是一個 char 和 3/4 個 int,不是一個完整的 int,爲了取出一個完整的 int 還得再取一次,這樣就很僵硬了。
  但是當我們把 char 補成四個字節,那麼每次就可以取出來完整的數據了,cpu 再去解析也就方便了很多。所以說內存對齊是爲了硬件計算起來效率更高設計的
在這裏插入圖片描述

總的來說,結構體的內存對齊是用 空間 換 時間 的做法

四、如何修改默認對齊數?

  藉助預處理指令 #pragma pack()
#pragma pack(2) :將默認對齊數修改爲2。默認對齊數一旦修改,那麼結構體計算的大小也就發生了相應的變化。
#pragma pack() :取消設置的默認對齊數,還原爲默認對齊數。

五、筆試題練習

提問:寫一個宏,計算結構體中某變量相對於首地址的偏移,並給出說明?
答:

typedef struct s1
{
	char a;
	int b;
	char c;
} s1;

#define OFFSET(a, b) ((char*)(&b) - (char*)(&a))

int main()
{
	s1 s;
	printf("成員 b 相對於首地址的偏移量爲:%d\n", OFFSET(s, s.b));
	system("pause");
	return 0;
}

如果直接用兩個指針進行相減,減出來肯定有問題,因爲這裏的指針是帶類型的,所以我們需要將指針強轉爲char*進行相減,那麼計算出來的就是字節數。
在這裏插入圖片描述


以上就是內存對齊的相關知識!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章