一.記憶體對齊的初步講解
記憶體對齊可以用一句話來概括:
「資料項只能儲存在位址是資料項大小的整數倍的記憶體位置上」
例如int型別占用4個位元組,位址只能在0,4,8等位置上。
例1:#include
struct xx;
int main()
執行結果如下:
&a = ffbff5ec
&b = ffbff5e8
&c = ffbff5f0
&d = ffbff5f4
sizeof(xx) = 16
會發現b與a之間空出了3個位元組,也就是說在b之後的0xffbff5e9,0xffbff5ea,0xffbff5eb空了出來,a直接儲存在了0x
ffbff5ec, 因為a的大小是4,只能儲存在4個整數倍的位置上。列印xx的大小會發現,是16,有些人可能要問,b之後空出了3個位元組,那也應該是13啊?其餘的3個 呢?這個往後閱讀本文會理解的更深入一點,這裡簡單說一下就是d後邊的3個位元組,也會浪費掉,也就是說,這3個位元組也被這個結構體占用了.
可以簡單的修改結構體的結構,來降低記憶體的使用,例如可以將結構體定義為:
struct xx;
這樣列印這個結構體的大小就是12,省了很多空間,可以看出,在定義結構體的時候,一定要考慮要記憶體對齊的影響,這樣能使我們的程式占用更小的記憶體。
二.作業系統的預設對齊係數
每 個作業系統都有自己的預設記憶體對齊係數,如果是新版本的作業系統,預設對齊係數一般都是8,因為作業系統定義的最大型別儲存單元就是8個位元組,例如 long long定要這樣,不存在超過8個位元組的型別(例如int是4,char是1,long在32位編譯時是4,64位編譯時是 8)。當作業系統的預設對齊係數與第一節所講的記憶體對齊的理論產生衝突時,以作業系統的對齊係數為基準。(例如 作業系統預設的對齊係數為 4 , 那麼char型別的資料就只能存在0 , 4 ,8 等4的倍數字址上;
假設作業系統的預設對齊係數是4,那麼對與long long這個型別的變數就不滿足第一節所說的,也就是說long long這種結構,可以儲存在被4整除的位置上,也可以儲存在被8整除的位置上。)
可以通過#pragma pack()語句修改作業系統的預設對齊係數,
例2:#include
#pragma pack(4)
struct xx;
#pragma pack()
int main()
列印結果為:
&a = ffbff5e4
&b = ffbff5e0
&c = ffbff5ec
&d = ffbff5f0
sizeof(xx) = 20
發現占用8個位元組的a,儲存在了不能被8整除的位置上,儲存在了被4整除的位置上,採取了作業系統的預設對齊係數。
三.記憶體對齊產生的原因
記憶體對齊是作業系統為了快速訪問記憶體而採取的一種策略,簡單來說,就是為了放置變數的二次訪問。作業系統在訪問記憶體 時,每次讀取一定的長度(這個長度就是作業系統的預設對齊係數,或者是預設對齊係數的整數倍)。如果沒有記憶體對齊時,為了讀取乙個變數是,會產生匯流排的二 次訪問。
例如假設沒有記憶體對齊,結構體xx的變數位置會出現如下情況:
struct xx;
作業系統先讀取0x
ffbff5e8-
0xffbff5ef的記憶體,然後在讀取
0xffbff5f0-
0xffbff5f8的記憶體,為了獲得值c,就需要將兩組記憶體合併,進行整合,這樣嚴重降低了記憶體的訪問效率。(這就涉及到了老生常談的問題,空間和效率哪個更重要?這裡不做討論)。
這樣大家就能理解為什麼結構體的第乙個變數,不管型別如何,都是能被8整除的吧(因為訪問記憶體是從8的整數倍開始的,為了增加讀取的效率)!
記憶體對齊的問題主要存在於理解struct等復合結構在記憶體中的分布。
首先要明白記憶體對齊的概念。
許多實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的首位址的值是某個數k(通常它為4或8)的倍數,這就是所謂的記憶體對齊。
這個k在不同的cpu平台下,不同的編譯器下表現也有所不同。比如32位字長的計算機與16位字長的計算機。我們的開發主要涉及兩大平台,windows和linux(unix),涉及的編譯器也主要是microsoft編譯器(如cl),和gcc。
記憶體對齊的目的是使各個基本資料型別的首位址為對應k的倍數,這是理解記憶體對齊方 式的終極法寶。另外還要區分編譯器的分別。明白了這兩點基本上就能搞定所有記憶體對齊方面的問題。
不同編譯器中的k:
1、對於microsoft的編譯器,每種基本型別的大小即為這個k。大體上char型別為8,int為32,long為32,double為64。
2、對於linux下的gcc編譯器,對於大於等於4位元組的成員的起始 位置應該處在4位元組的整數倍上,對於2位元組成員的起始位置應該處在2位元組的整數倍上,1位元組的起始位置任意。
明白了以上的說明對struct等復合結構的記憶體分布就應該很清楚了。
下面看一下最簡單的乙個型別:struct中成員都為基本資料型別,例如:
struct test1
;在windows平台,microsoft編譯器下:
假設從0位址開始,首先a的k值為1,它的首位址可以使任意位置,所以a占用第乙個位元組,即位址0;然後b的k值為2,他的首位址必須是2的倍數,不能是1,所以位址1那個位元組被填充,b首位址為位址2,占用位址2,3;然後到c,c的k值為4,他的首位址為4的倍數,所以首位址為4,占用位址4,5,6,7;再然後到d,d的k值也為4,所以他的首位址為8,占用位址8,9,10,11。最後到e,他的k值為8,首位址為8的倍數,所以位址12,13,14,15被填充,他的首位址應為16,占用位址16-23。顯然其大小為24。
在linux平台,gcc編譯器下:
假設從0位址開始,首先a的k值為1,它的首位址可以使任意位置,所以a占用第乙個位元組,即位址0;然後b的k值為2,他的首位址必須是2的倍數,不能是1,所以位址1那個位元組被填充,b首位址為位址2,占用位址2,3;然後到c,c的k值為4,他的首位址為4的倍數,所以首位址為4,占用位址4,5,6,7;再然後到d,d的k值也為4,所以他的首位址為8,占用位址8,9,10,11。最後到e,從這裡開始與microsoft的編譯器開始有所差異,他的k值為不是8,仍然是4,所以其首位址是12,占用位址12-19。顯然其大小為20。
接下來,看一看幾類特殊的情況,為了避免麻煩,不再描述記憶體分布,只計算結構大小。
第一種:巢狀的結構
struct test2
;在windows平台,microsoft編譯器下:
這種情況下如果把test2的第二個成員拆開來,研究記憶體分布,那麼可以知道,test2的成員f占用位址0,g.a占用位址1,以後的記憶體分布不變,仍然滿足所有基本資料成員的首位址都為其對應k的倍數這一原則,那麼test2的大小就還是24了。但是實際上test2的大小為32,這是因為:不能因為test2的結構而改變test1的記憶體分布情況,所以為了使test1種各個成員仍然滿足對齊的要求,f成員後面需要填充一定數量的位元組,(可能是windows預設的對齊係數為8 , 所以struct需要從8的倍數開始儲存,因為對於結構體,它的大小應該是編譯器最大對齊模數的整數倍))不難發現,這個數量應為7個,才能保證test1的對齊。所以test2相對於test1來說增加了8個位元組,所以test2的大小為32。
在linux平台,gcc編譯器下:
同樣,這種情況下如果把test2的第二個成員拆開來,研究記憶體分布,那麼可以知道,test2的成員f占用位址0,g.a占用位址1,以後的記憶體分布不變,仍然滿足所有基本資料成員的首位址都為其對應k的倍數這一原則,那麼test2的大小就還是20了。但是實際上test2的大小為24,同樣這是因為:不能因為test2的結構而改變test1的記憶體分布情況,所以為了使test1種各個成員仍然滿足對齊的要求,f成員後面需要填充一定數量的位元組(估計是因為linux的預設對齊係數為4 , 所以struct成員需要從4 的倍數開始儲存,因為對於結構體,它的大小應該是編譯器最大對齊模數的整數倍),不難發現,這個數量應為3個,才能保證test1的對齊。所以test2相對於test1來說增加了4個位元組,所以test2的大小為24。
關於巢狀的struct的儲存仍然不明白, 待以後更改。
關於記憶體對齊
資料傳送到網路板的資料報大小根本不是實際控制數 據包的大小 這時我才想起乙個人,stanley b.lippman,他寫的那 一本書 inside object modale 曾經提過這樣的事 情,編譯器為了提高cpu的效率,會對struct 的結構進行優化,利用sizeof 可以得出不同的計算機上...
關於記憶體對齊
早上看了乙個貼的面試題,struct st int i short s char c sizeof struct st 是多少?int 4,short 2,char 1,但是sizeof st 是8。這個就是記憶體對齊 再來看個例子 struct strt1 strt1 s1 假設s1.c1位址為0...
關於記憶體對齊
資料傳送到網路板的資料報大小根本不是實際控制資料報的大小 這時我才想起乙個人,stanley b.lippman,他寫的那一本書 inside object modale 曾經提過這樣的事情,編譯器為了提高cpu的效率,會對struct 的結構進行優化,利用sizeof 可以得出不同的計算機上對 s...