內存對齊的思考


結構體的內存佈局依賴於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和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)在默認情況下采用如下的對齊規則: 任何基本數據類型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就僅僅是讓它們一樣嚴格)。

 
             
             
             
             
             
             
             
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章