c/c++ struct記憶體對齊

來源:互聯網
上載者:User
c/c++ struct記憶體對齊

程式設計語言 2009-07-25 13:22:38 閱讀596 評論2   字型大小:大小 訂閱

記憶體對齊

結構體的記憶體布局依賴於CPU、作業系統、編譯器及編譯時間的對齊選項。結構體內部成員的對齊要求,結構體本身的對齊要求。最重要的有三點

(一)成員對齊。對於結構體內部成員,通常會有這樣的規定:各成員變數存放 的起始地址相對於結構的起始地址的位移量必須為該變數的類型所佔用的位元組數的倍數。但是也可以看到,有時候某些欄位如果嚴格按照大小緊密排列,根本無法達到這樣的目的,因此有時候必須進行padding。各成員變數在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊調整位置,空缺的位元組編譯器會自動填滿也就是padding。

(二)然後,還要考慮整個結構體的對齊需求。ANSI C標準規定結構體類型的對齊要求不能比它所有欄位中要求最嚴格的那個寬鬆,可以更嚴格。實際上要求結構體至少是其中的那個最大的元素大小的整數倍。因為有時候我們使用的是結構體數組,所以結構體的大小還得保證結構體數組中各個結構體滿足對齊要求,同時獨立的結構體與結構體數組中單個結構體的大小應當是一致的。

(三)編譯器的對齊指令。VC 中提供了#pragma pack(n)來設定變數以n位元組對齊。n位元組對齊就是說變數存放的起始地址的位移量有兩種情況:第一、如果n大於等於該變數所佔用的位元組數,那麼偏 移量必須滿足預設的對齊,第二、如果n小於該變數的類型所佔用的位元組數,那麼位移量為n的倍數,不用滿足預設的對齊。結構的總大小也有個約束條 件,分下面兩種情況:如果n大於所有成員變數類型所佔用的位元組數,那麼結構的總大小必須為佔用空間最大的變數佔用的空間數的倍數。

       規則:http://bigwhite.blogbus.com/logs/1347304.html
1、資料成員對齊規則:結構(struct)(或聯合(union))的資料成員,第一個資料成員放在offset為0的地方,以後每個資料成員的對齊按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行。
3、結合1、2推斷:當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果。

總結一下:
成員對齊有一個重要的條件,即每個成員分別對齊.即每個成員按自己的方式對齊.如果有#pragma pack(8),它雖然指定了按8位元組對齊,但並不是所有的成員都是以8位元組對齊.其對齊的規則是,每個成員按類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這裡是8位元組)中較小的一個對齊.並且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空位元組.也就是說對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊。實際上根據這些規則安排整個的記憶體布局的演算法很簡單,假設起始地址為0,開始安放第1個成員,然後找到下一個成員可以安放的起始位置,首先這個位置肯定在第一個成員之外,其次滿足那些對齊因素。找到滿足這兩個條件的第一個位置即可。然後再考慮下一個成員,逐次進行下去。最後再考慮整個結構體的對齊因素,確定整個結構體的結束位置,這個位置的下個位置也就是下一個結構體的開始位置,保證它能夠滿足對齊。

比如:
struct MyStruct
{
char dda;
double dda1;  
int type
};

(簡單說明)
struct MyStruct
{
char dda;//位移量為0,滿足對齊,dda佔用1個位元組;
double dda1;//下一個可用的地址的位移量為1,不是sizeof(double)=8
             //的倍數,需要補足7個位元組才能使位移量變為8(滿足對齊
             //方式),因此VC自動填滿7個位元組,dda1存放在位移量為8
             //的地址上,它佔用8個位元組。
int type;//下一個可用的地址的位移量為16,是sizeof(int)=4的倍
           //數,滿足int的對齊,所以不需要VC自動填滿,type存
           //放在位移量為16的地址上,它佔用4個位元組。
};//所有成員變數都分配了空間,空間總的大小為1+7+8+4=20,不是結構
   //的節邊界數(即結構中佔用最大空間的類型所佔用的位元組數sizeof
   //(double)=8)的倍數,所以需要填充4個位元組,以滿足結構的大小為
   //sizeof(double)=8的倍數。
所以該結構總的大小為:sizeof(MyStruc)為1+7+8+4+4=24。其中總的有7+4=11個位元組是VC自動填滿的,沒有放任何有意義的東西。

       為何要記憶體對齊

http://www.ibm.com/developerworks/library/pa-dalign/

因為處理器讀寫資料,並不是以位元組為單位,而是以塊(2,4,8,16位元組)為單位進行的。如果不進行對齊,那麼本來只需要一次進行的訪問,可能需要好幾次才能完成,並且還要進行額外的merger或者資料分離。導致效率低下。更嚴重地,會因為cpu不允許訪問unaligned address,就會報錯,或者開啟調試器或者dump core,比如sun sparc solaris絕對不會容忍你訪問unaligned address,都會以一個core結束你的程式的執行。所以一般編譯器都會在編譯時間做相應的最佳化以保證程式運行時所有資料都是儲存在'aligned address'上的,這就是記憶體對齊的由來。

在'Data alignment: Straighten up and fly right'這篇文章中作者還得出一個結論那就是:"如果訪問的地址是unaligned的,那麼採用大粒度訪問記憶體有可能比小粒度訪問記憶體還要慢"。

位域

http://www.ksarea.com/articles/20071004_sizeof-struct-memory.html

如果結構體中含有位域(bit-field),那麼VC中準則又要有所更改:
1) 如果相鄰位域欄位的類型相同,且其位寬之和小於類型的sizeof大小,則後面的欄位將緊鄰前一個欄位儲存,直到不能容納為止;
2) 如果相鄰位域欄位的類型相同,但其位寬之和大於類型的sizeof大小,則後面的欄位將從新的儲存單元開始,其位移量為其類型大小的整數倍;
3) 如果相鄰的位域欄位的類型不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式(不同位域欄位存放在不同的位域類型位元組中),Dev-C++和GCC都採取壓縮方式;
備忘:當兩欄位類型不一樣的時候,對於不壓縮方式,例如:

struct N
{
  char c:2;
  int    i:4;
};
依然要滿足不含位域結構體記憶體對齊準則第2條,i成員相對於結構體首地址的位移應該是4的整數倍,所以c成員後要填充3個位元組,然後再開闢4個位元組的空間作為int型,其中4位用來存放i,所以上面結構體在VC中所佔空間為8個位元組;而對於採用壓縮方式的編譯器來說,遵循不含位域結構體記憶體對齊準則第2條,不同的是,如果填充的3個位元組能容納後面成員的位,則壓縮到填充位元組中,不能容納,則要單獨開闢空間,所以上面結構體N在GCC或者Dev-C++中所佔空間應該是4個位元組。

4) 如果位域欄位之間穿插著非位域欄位,則不進行壓縮;
備忘:
結構體

typedef struct
{
   char c:2;
   double i;
   int c2:4;
}N3;
在GCC下佔據的空間為16位元組,在VC下佔據的空間應該是24個位元組。
5) 整個結構體的總大小為最寬基本類型成員大小的整數倍。

        看一段引用
##################################################################
http://blog.csdn.net/manbug/archive/2006/08/26/1124845.aspx
  首先,至少有一點可以肯定,那就是ANSI C保證結構體中各欄位在記憶體中出現的位置是隨它們的聲明順序依次遞增的,並且第一個欄位的首地址等於整個結構體執行個體的首地址。這時,有朋友可能會問:"標準是否規定相鄰欄位在記憶體中也相鄰?"。 唔,對不起,ANSI C沒有做出保證,你的程式在任何時候都不應該依賴這個假設。那這是否意味著我們永遠無法勾勒出一幅更清晰更精確的結構體記憶體布局圖?哦,當然不是。不過先讓我們從這個問題中暫時抽身,關注一下另一個重要問題————記憶體對齊。

許多實際的電腦系統對基本類型資料在記憶體中存放的位置有限制,它們會要求這些資料的首地址的值是某個數k(通常它為4或8)的倍數,這就是所謂的記憶體對齊,而這個k則被稱為該資料類型的對齊模數(alignment modulus)。當一種類型S的對齊模數與另一種類型T的對齊模數的比值是大於1的整數,我們就稱類型S的對齊要求比T強(嚴格),而稱T比S弱(寬鬆)。這種強制的要求一來簡化了處理器與記憶體之間傳輸系統的設計,二來可以提升讀取資料的速度。比如這麼一種處理器,它每次讀寫記憶體的時候都從某個8倍數的地址開始,一次讀出或寫入8個位元組的資料,假如軟體能保證double類型的資料都從8倍數地址開始,那麼讀或寫一個double類型資料就只需要一次記憶體操作。否則,我們就可能需要兩次記憶體操作才能完成這個動作,因為資料或許恰好橫跨在兩個符合對齊要求的8位元組記憶體塊上。某些處理器在資料不滿足對齊要求的情況下可能會出錯,但是Intel的IA32架構的處理器則不管資料是否對齊都能正確工作。不過Intel奉勸大家,如果想提升效能,那麼所有的程式資料都應該儘可能地對齊。Win32平台下的微軟C編譯器(cl.exe for 80x86)在預設情況下採用如下的對齊規則: 任何基礎資料型別 (Elementary Data Type)T的對齊模數就是T的大小,即sizeof(T)。比如對於double類型8位元組),就要求該類型資料的地址總是8的倍數,而char類型資料(1位元組)則可以從任何一個地址開始。

Linux下的GCC奉行的是另外一套規則(在資料中查得,並未驗證,如錯誤請指正):任何2位元組大小(包括單位元組嗎?)的資料類型(比如short)的對齊模數是2,而其它所有超過2位元組的資料類型(比如long,double)都以4為對齊模數。

現在回到我們關心的struct上來。ANSI C規定一種結構類型的大小是它所有欄位的大小以及欄位之間或欄位尾部的填充區大小之和。嗯?填充區?對,這就是為了使結構體欄位滿足記憶體對齊要求而額外分配給結構體的空間。那麼結構體本身有什麼對齊要求嗎?有的,ANSI C標準規定結構體類型的對齊要求不能比它所有欄位中要求最嚴格的那個寬鬆,可以更嚴格(但此非強制要求,VC7.1就僅僅是讓它們一樣嚴格)。

在實際開發中,我們可以通過指定/Zp編譯選項來更改編譯器的對齊規則。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告訴編譯器最大對齊模數是n。在這種情況下,所有小於等於n位元組的基礎資料型別 (Elementary Data Type)的對齊規則與預設的一樣,但是大於n個位元組的資料類型的對齊模數被限制為n。事實上,VC7.1的預設對齊選項就相當於/Zp8。仔細看看MSDN對這個選項的描述,會發現它鄭重告誡了程式員不要在MIPS和Alpha平台上用/Zp1和/Zp2選項,也不要在16位平台上指定/Zp4和/Zp8(想想為什嗎?)。
##################################################################

參考文獻
再談記憶體對齊問題-http://blog.ednchina.com/jasony/92132/message.aspx
也談記憶體對齊-http://bigwhite.blogbus.com/logs/1347304.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.