一、爲什麼會有內存對齊?
字,雙字,和四字在自然邊界上不需要在內存中對齊。(對字,雙字,和四字來說,自然邊界分別是偶數地址,可以被4整除的地址,和可以被8整除的地址。)
無論如何,爲了提高程序的性能,數據結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,爲了訪問未對齊的內存,處理器需要作兩次內存訪問;然而,對齊的內存訪問僅需要一次訪問。
一個字或雙字操作數跨越了4字節邊界,或者一個四字操作數跨越了8字節邊界,被認爲是未對齊的,從而需要兩次總線週期來訪問內存。一個字起始地址是奇數但卻沒有跨越字邊界被認爲是對齊的,能夠在一個總線週期中被訪問。
某些操作雙四字的指令需要內存操作數在自然邊界上對齊。如果操作數沒有對齊,這些指令將會產生一個通用保護異常。雙四字的自然邊界是能夠被16整除的地址。其他的操作雙四字的指令允許未對齊的訪問(不會產生通用保護異常),然而,需要額外的內存總線週期來訪問內存中未對齊的數據。
簡而言之,數據結構(尤其是棧)應該儘可能地在自然邊界上對齊,原因在於,爲了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
二、C++內存對齊規則
每個特定平臺上的編譯器都有自己的默認“對齊係數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。
對齊規則:
1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,以後每個數據成員的對齊按照 #pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
3、結合1、2推斷:當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。
4.各成員變量存放的起始地址相對於結構的起始地址的偏移量必須爲該變量的類型所佔用的字節數的倍數。
5.各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節自動填充。
6.同時爲了確保結構的大小爲結構的字節邊界數(即該結構中佔用最大空間的類型所佔用的字節數)的倍數,所以在爲最後一個成員變量申請空間後,還會根據需要自動填充空缺的字節。
(此兩點最爲重要)
#include <iostream>
using namespace std;
class X1
{
int i;//4個字節
char c1;//1個字節
char c2;//1個字節
public:
X1()
{
i=0x98765432;
c1=0x12;
c2=0x21;
}
};
class X2
{
char c1;//1個字節
int i;//4個字節
char c2;//1個字節
public:
X2()
{
i=0x98765432;
c1=0x12;
c2=0x21;
}
};
class X3
{
char c1;//1個字節
char c2;//1個字節
int i;//4個字節
public:
X3()
{
i=0x98765432;
c1=0x12;
c2=0x21;
}
};
int main()
{
cout<<"long "<<sizeof(long)<<"\n";
cout<<"float "<<sizeof(float)<<"\n";
cout<<"int "<<sizeof(int)<<"\n";
cout<<"char "<<sizeof(char)<<"\n";
X1 x1;
X2 x2;
X3 x3;
cout<<"x1 的大小 "<<sizeof(x1)<<"\n";
cout<<"x2 的大小 "<<sizeof(x2)<<"\n";
cout<<"x3 的大小 "<<sizeof(x3)<<"\n";
return 0;
}
圖中類對象x1,x2,x3的地址分別爲0x12ff5C,0x12ff48,0x12ff38: