結構體內存對齊

爲什麼會出現內存對齊?

    因爲當CPU訪問內存對齊的數據時,它的運行效率是非常高的。當CPU試圖讀取的數值沒有正確的對齊時,CPU可以執行兩種操作之一:產生一個異常條件;執行多次對齊的內存訪問,以便讀取完整的未對齊數據,若多次執行內存訪問,應用程序的運行速度就會慢。所以計算機採用內存對齊的方式來存儲數據。

這是高效編程一種很重要的思想:以空間換時間

關於結構體內存對齊(沒有指定#pragma pack宏的情況):

規則1:數據成員對齊規則:結構體的數據成員,第一個數據成員放在offset(偏移量)爲0的地方,以後每個數據成員存儲的起始位置要從該成員大小的整數倍開始(比如int在32位機爲4字節,則要從4的整數倍地址開始存儲)。

規則2:結構體作爲成員:如果一個結構裏有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲。(struct a裏存有struct b,b裏有char,int,double等元素,那b應該從8的整數倍開始存儲。)

規則3:收尾工作:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員的整數倍,不足的要補齊。

看下面這個例子:

void Test1()
{
	struct A
	{
		int a;		//0-3
		double d;	//4-11
		char c;	//12
	};
	cout<<"sizeof(A)= "<<sizeof(A)<<endl;
}

先看規則1:第一個成員放在offset爲0的位置,a(0-3),第二個數據d起始位置爲第一個成員大小的整數倍開始,所以從4開始,double佔8個字節,所以d存放位置offset爲:(4-11),接下來的成員c,第一個成員整數倍爲它的起始位置,所以起始位置offset爲:(12),所以c存放位置(12),這就將數據按內存對齊方式存好了,但是結構體A的大小是多少呢?還要使用規則3:是結構體內部所佔字節最大的成員的最小整數倍。,最大成員是d(佔8個字節),所以sizeof(A) =  24,結果顯示:

wKiom1ca9E3i6CQIAAAK0zYHweA851.png

再看下面這個例子:

void Test2()
{
	struct B
	{
		int a;		//0-3
		char c;	//4
		double d;	//8-15
	};
	cout<<"sizeof(B) = "<<sizeof(B)<<endl;
}

還是先看規則1:第一個成員放在offset爲0的位置,a(0-3),第二個數據d起始位置爲第一個成員大小的整數倍開始,所以從4開始,char佔一個字節,所以成員c存放位置offset爲:(4),第三個數據成員起始位置依舊爲第一個成員大小的整數倍,起始位置offset爲(8),double所佔字節爲(8),所以d存放位置offset(8-15),再看規則3:結構體總大小,是結構體內部所佔字節最大的成員的最小整數倍。最大成員爲d,d佔8個字節,所以sizeof(B) = 16,顯示結果:

wKioL1ca-D_QTJVFAAALhOSm82k810.png

看這個例子:

void Test3()
{
	struct C
	{};
	cout<<"sizeof(C) = "<<sizeof(C)<<endl;
}//[cpp]

class D
{};

void Test4()
{
        cout<<"sizeof(D) = "<<sizeof(D)<<endl;
}

這個大小又是多少呢?哈哈..

因爲在C++標準中規定:任何不同的對象不能擁有相同的內存地址。如果空類大小爲0,若我們聲明一個這個類的對象數組,那麼數組中的每個對象都擁有了相同的地址,這顯然是違背標準的。

這時編譯器就會加一個字節來區別,所以sizeof(C) = 1,sizeof(D) = 1,顯示結果:

wKiom1ca_eSBDeDKAAALcWen7E0306.png

總結一下:當結構體中沒有嵌套時,只需要使用規則1和規則3就可以計算出結構體大小。

再來看下面這個例子:

void Test5()
{
	struct A
	{
		int a;		//0-3
		char c;		//4
		double d;       //8-15
	};//0-15
	struct B
	{
		A a;		//0-15
		int b;		//16-19
		double c;	//20-27
	};//0-31
	cout<<"sizeof(B) = "<<sizeof(B)<<endl;
}

還是先來看規則1來計算出結構體A的大小爲16,在結構體B中,a的大小爲16,結構體嵌套時就要運用規則2:結構體a的起始位置offset爲結構體A中最大成員的整數倍,所以在結構體B中成員a的起始位置offset爲:(0),所佔大小爲16,所以存放位置offset爲(0-15),而B的數據成員依舊按照規則1進行存儲:所以b的起始位置offset爲(16),存放位置offset爲(16-19),B中成員c的起始位置offset爲(20),存放位置offset爲(20-27),再依據規則3,結構B的總大小爲,結構體B中結構體內部所佔字節最大的成員的最小整數倍,不足的要補齊offset(28-31),所以sizeof(B) =  32,結構顯示:

wKioL1cbCcaA8DkxAAALj2BbmKI626.png

總結一下:當出現結構體嵌套時,首先依據規則1,計算出嵌套的結構體大小,再根據規則2,確定嵌套的結構體存儲的起始位置offset,再依據規則1,確定其其他成員的存儲位置,最後依據規則3,確定結構體其大小。

當指定了#pragma back宏時

看下面這個例子:

#pragma pack(4)   //指定默認最大對齊數爲4
void Test6()
{
	struct A
	{
		char c1;    //0
		int i;      //4-7
		double d;   //8-15
		char c2;    //16
	};
	cout<<"sizeof(A) = "<<sizeof(A)<<endl;
}

當沒有指定#pragma back宏時,依據規則1和規則3,sizeof(A) =  24,有了默認最大對齊數,規則3就不在適用了,應該改爲指定最大默認對齊數的最小整數倍,所以sizeof(A) = 20。顯示結果:

wKiom1cbDhbD_lOmAAALes8dTTM154.png

那麼再來看下面這個例子:

#pragma pack(4)
void Test7()
{
	struct A
	{
		char arr[7];
		short s;
	};
	cout<<"sizeof(A) = "<<sizeof(A)<<endl;;
}

由於有了上面的Test6(),就可以知道:sizeof(A) = 12,顯示結果:

wKiom1cbD3_C_IxVAAAMYeyRjo8847.png

爲什麼是10,瞬間懵逼...j_0012.gif

因爲結構體A中最大對齊數比默認對齊數小,所以使用規則3時,就使用結構體中的最大對齊數。

總結一下:當有指定默認對齊數時,就要分情況考慮了。

    如果默認對齊數比結構體內部的最大對齊數小,那麼使用規則3時,結構體大小就是默認對齊數的最小整數倍;

    如果默認對齊數比結構體內部的最大對齊數大,就直接使用規則3.

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