內存對齊

本次主要討論三個問題:

  1. 什么是內存對齊
  2. 內存對齊的好處
  3. 如何對齊

內存對齊

內存對齊是一種提高內存訪問速度的策略。編譯器為程序中的每個數據單元安排在適當的位置上。計算機系統(tǒng)對基本類型數據在內存中存放的位置有限制,它們要求這些數據的首地址的值是某個數M(通常是4或8,Windows中默認對齊數為8,Linux中默認對齊數為4);為了提高程序的性能,數據結構,特別是棧,應盡可能在自然邊界上對齊,經過對齊后,cpu的內存訪問速度大大提升。

內存的自然對齊,每一種數據類型都必須放在地址中的整數倍上。例如:地址4可以放char(1)類型,可以放int(4)型,可以放short(2)型,但是不能存放double(8)型,僅僅因為4不是8的整數倍。地址3能存放char型,但是其他int,short,double都不能存放。有一個特殊地址,就是0,它可以是任何類型的整數倍,所以可以存放任何數據。根據這個規(guī)則,那么在分配一大塊包含很多變量的內存的時候,會產生很多碎片。

內存對齊的好處

為了提高效率,計算機從內存中取數據是按照一個固定長度的來取的。以32位機為例,它每次取32個位,也就是4個字節(jié)(每字節(jié)8個位,計算機基礎知識)。每個總線周期都是從偶地址開始讀取32位的內存數據,如果數據存放地址不是從偶數開始,則可能出現需要兩個總線周期才能讀取到想要的數據。

以int型數據為例,如果它在內存中存放的位置按4字節(jié)對齊,也就是說1個int的數據全部落在計算機一次取數的區(qū)間內,那么只需要取一次就可以了。如果不對齊,很不巧,這個int數據剛好跨越了取數的邊界,這樣就需要取兩次才能把這個int的數據全部取到,這樣效率也就降低了。

另外一個是平臺原因(移植原因), 不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

另外,不同的編譯器可能會對內存的分布進行優(yōu)化,但這屬于編譯器的問題,暫不做過多討論。

如何對齊

以struct為例:

struct A{
char a;
int b;
short c;
}

在32位機器上char 占1個字節(jié),int 占4個字節(jié),short占2個字節(jié),一共占用7個字節(jié),但實際上并非如此。測試輸出的結果是A: 12, 比計算的7多了5個字節(jié)。這個就是因為編譯器在編譯的時候進行了內存對齊導致的。

內存對齊主要遵循下面三個原則:

結構體變量的起始地址能夠被其最寬的成員大小整除
結構體每個成員相對于起始地址的偏移能夠被其自身大小整除,如果不能則在前一個成員后面補充字節(jié)
結構體總體大小能夠被最寬的成員的大小整除,如不能則在后面補充字節(jié)

在內存中,編譯器按照成員列表順序分別為每個結構體變量成員分配內存,當存儲過程中需要滿足邊界對齊的 要求時,編譯器會在成員之間留下額外的內存空間。如char=1,short=2,int=4,double=8。所謂自對齊,指的是該成員的起始位置的內存地址必須是它自身長度的整數倍。如int只能以0,4,8這類的地址開始。

編譯器在編譯的時候是可以指定對齊大小的,實際使用的有效對齊其實是取指定大小和自身大小的最小值,一般默認的對齊大小是4。結構體的有效對齊值規(guī)定如下:

1)當未明確指定時,以結構體中最長的成員的長度為其有效值

2)當用#pragma pack(n)指定時,以n和結構體中最長的成員的長度中較小者為其值

3)當用__attribute__ ((__packed__))指定長度時,強制按照此值為結構體的有效對齊值

如果默認的對齊大小是4,結構體a的其實地址為0x0000,能夠被最寬的數據成員大小(這里是int, 大小為4,有效對齊大小也是4)整除,故char a的從0x0000開始存放占用一個字節(jié)即0x0000~0x0001,然后是int b,其大小為4,需要滿足第二個原則,從0x0004開始,所以在char a后填充三個字節(jié),因此a對齊后占用的空間是0x0000~0x0003,b占用的空間是0x0004~0x0007, 然后是short c其大小是2,故從0x0008開始占用兩個字節(jié),即0x0008~0x000A。 此時整個結構體占用的空間是0x0000~0x000A, 占用11個字節(jié),11%4 != 0, 不滿足第三個原則,所以需要在后面補充一個字節(jié),即最后內存對齊后占用的空間是0x0000~0x000B,一共12個字節(jié)。

當前的使用

內存對齊本身對程序員來說是透明的,即程序員該取變量就取變量,該存就存,編譯程序時編譯器會把變量按本身的平臺進行對齊。但如果你要寫一個內存池(如boost的ordered_pool),或者使用了reinterpret_cast這種對內存直接進行操作的函數,這方面還是要注意一下的,即使CPU支持,效率也會受到影響。

swift 的 json轉換工具庫 HandyJSON做處理時為了繞過反射賦值,直接寫入內存,就用到了內存對齊。類實例的屬性并不是直接按照各自占位大小依次往下排列的,和C/C++一樣,Swift中實例內存布局也考慮了內存對齊。對齊的規(guī)則為,每個字段的起始地址必須為alignment值的整數倍。以此來計算下一個字段起始地址。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容