C 記憶體對齊詳解

2021-06-05 06:04:04 字數 3239 閱讀 5379

最近看各公司筆試和面試的試題,不少是關於c++記憶體對齊方面的。這個問題我以前也模模糊糊的了解一些,但總是不甚清楚。這次費了很大勁,終於算是搞明白了。整理出來,和大家分享一下。

這一切要從機器字長和儲存字長說起。機器字長是cpu每次處理的二進位制的位數;儲存字長是記憶體中乙個儲存單元的包含二進位制位數,或一次記憶體讀寫操作的位數,也可以理解成資料線的根數。

下面以機器字長和儲存字長都是32位的機器為例,來說明為什麼要記憶體對齊。我們知道記憶體是以位元組(byte)來編址的,32位也就是4個位元組。由於記憶體的讀寫單位是儲存單元,所以cpu對記憶體進行讀寫時,傳送給它的位址必須是儲存單元長度的倍數,即儲存字長的倍數。我們這裡的儲存字長是4 bytes,所以這個位址必須是4n。假設現有乙個大小為4 bytes的int型別的值,如不考慮對齊,它在記憶體中的儲存有以下4種情況:

圖 1

case 1時,處理器只需要讀乙個儲存單元,就可以直接得到int值; case 2、3、4時,就需要讀取兩個記憶體單元,並且要經過一系列處理後才能得到所需的值,這種情況下效率是很低的。而這種糟糕情況,對於int型別來說,發生的概率是75%。若果不對齊,cpu對記憶體的讀寫是很低效的。

在討論對齊規則之前,先把我用來測試的環境說一下。編譯器vc6.0,作業系統win7,機器字長、儲存字長32(4bytes)。此環境下基本資料的大小為:sizeof(char)=1;sizeof(short)=2; sizeof(int)=4; sizeof(double)=8。

設儲存字長為w bytes,機器字長不小於w。根據前面的分析,我們從記憶體訪問的效率出發,很容易得到乙個原則每個基本資料型別t訪問時,訪問的儲存單元的個數應為ceiling(sizeof(t)/w),而不應超出此值。這就是記憶體對齊的基本原則。在進一步討論之前,我們先引入乙個概念:對齊大小。對於基本資料型別而言,對齊大小是sizeof的返回值;而對於復合型別,則是其資料成員中最大的對齊大小的值,sizeof的返回值是它的儲存大小。比如:

struct t

;

t的對齊大小為sizeof(int)=4,儲存大小是2*4=8。

以下是記憶體對其的規則,

規則二:復合型別的的儲存大小為其對齊大小的整數倍。

規則三:復合型別包含復合型別的時候,子結構按乙個整體對待,儲存大小不變。

在基本資料型別的大小都是2整數冪的前提下,這個規則滿足基本原則的要求。幸運的是c++的基本資料型別確實全都是2的整數冪(bool型別的提公升為乙個位元組處理),vc6.0在預設情況下,也確實用的這一規則。這乙個規則的好處是,不必關心儲存字長w,就可以滿足16、32、64乃至128位儲存字長下,記憶體對齊的要求。以下通過例子進行說明。

例1.

#includeusing std::cout;

using std::endl;

struct a

;int main()

;b testb;

cout執行結果:

型別b和例1中的a其實是一樣的,只是改變了內部元素的宣告順序,得到的結果便發生了改變,最明顯的就是它節省了4 bytes。根據輸出資訊,我們得到b的資料在記憶體中存放的示意圖:

圖 3

根據規則一,把資料元素排列後,用去8 bytes。這個大小正好滿足規則二,也就不再需要補充了,所以b的儲存大小是8 bytes。

例 3.

struct c

;c testc;

cout資料在記憶體中存放的示意圖:

圖 4

與以上例子一樣,先根據規則一將資料安排好,由於此結構中最大元素doubleb的大小為8 bytes,按8的倍數補齊,c的大小即為32 bytes。不過仔細一數,發現實際用到的位元組數只有15 bytes,而浪費掉的卻有17 bytes之多。現在記憶體容量都比較大,也許不需要太計較,但作為乙個以勤儉節約為傳統美德的民族的子民,看到這耀眼的空白,總有點誠惶誠恐的感覺。根據結構a和b的經驗,我們調整一下結構。

例4.struct d

;d testd;

cout資料在記憶體中存放的示意圖:

圖5與圖4相比,這個是不是看上去好了很多。不錯,它確實節省了一半的記憶體。通過這幾個例子我們可以得到乙個經驗,在設計新復合型別的時候,我們應盡量把相同型別的資料放在一塊;不同型別的資料之間按儲存大小遞增或遞減的方式排列,這樣就可以保證大多數情況下浪費的空間最少。

下面說說結構中包含結構的情況。

例5.struct suba

;struct subb

;suba testsuba;

subb testsubb;

cout<

由此可到suba和subb的資料在記憶體中的示意圖:

圖 6在此基礎上定義

struct e

;e teste;

cout<

圖 7可以看出,給e分配儲存空間時,先在0位置安放suba a,占去了6 bytes。接下來安排char b和int c,在安排subb之前共用去了12 bytes。根據規則三,subb按整體對待。由於它的對齊大小是8,起始位址必須是8n,於是跳過4bytes在位置16安放,占去sizeof(subb)=16個位元組。至此,e占用了32bytes, e的對齊大小等於subb的對齊大小為8,32=4*8,滿足規則二,不需要補齊,這就得到了e的儲存大小32bytes。 

到這裡,vc6.0的預設對齊方式就算說完了。也許你發現,subb的儲存浪費了將近50%的記憶體,而我們也確實沒辦法進一步優化它。如果你的程式注重空間而不太要求效率的話,這確實會讓你很難受。幸運的是,好的編譯器總會提供你更多的選擇。預編譯命令#pragma pack(n)可以幫你解決這一問題,它允許你設定資料的對齊大小。編譯器建議你這個n值取2的整數冪,不然它會給你乙個警告,然後不理你的設定。即便按要求設定了n,編譯器也不一定會用,它真正使用的值是,你設定的n和型別預設對齊大小中較小的乙個,即min。是不是看著很繞,沒關係,先看個例子。

例6.#includeusing std::cout;

using std::endl;

#pragma pack(2)//語句1

struct f

;int main()

=2,所以int的對齊大小為2,因為位置2滿足了規則一,所以可以在此儲存b。然後分配char,min=1,也就是說char的對齊大小沒有改變,直接儲存就行,最後補成2的倍數,便得sizeof(f)=8.

ok,這就我理解的全部內容。鑑於本人水平有限,且對底層不甚了解,文中難免出現這樣那樣的錯誤,還望高手雅正。

詳解C語言記憶體對齊

在c語言裡有乙個機制是記憶體對齊,當然不止c語言,包括其他的程式語言都會有記憶體對齊機制,否則編譯出來的軟體無法正常執行,至於為什麼呢?眾所周知,在記憶體中,所有的資料都是按位元組為最小單位儲存的,儲存單位稱為儲存單元,所以也叫記憶體是由很多儲存單元組成的,這些儲存單元 位元組 都有固定的位址標示著...

c語言記憶體對齊詳解

一 什麼是位元組對齊,為什麼要對齊?現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定型別變數的時候經常在特定的記憶體位址訪問,這就需要各種型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是對齊。對齊的作...

記憶體對齊詳解

首先由乙個程式引入話題 程式的輸出結果為 sizeof st1 is 12 sizeof st2 is 8 問題出來了,這兩個一樣的結構體,為什麼sizeof的時候大小不一樣呢?對於大多數的程式設計師來說,記憶體對齊基本上是透明的,這是編譯器該幹的活,編譯器為程式中的每個資料單元安排在合適的位置上,...