內存對齊

一、內存對齊的原因
大部分的參考資料都是如是說的:
1、平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
2、性能原因:數據結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,爲了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。

   也有的朋友說,內存對齊出於對讀取的效率和數據的安全的考慮,我覺得也有一定的道理。

二、對齊規則
    每個特定平臺上的編譯器都有自己的默認“對齊係數”(也叫對齊模數)。比如32位windows平臺下,VC默認是按照8bytes對齊的(VC->Project->settings->c/c++->Code Generation中的truct member alignment 值默認是8),程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。

    在嵌入式環境下,對齊往往與數據類型有關,特別是C編譯器對缺省的結構成員自然對屆條件爲“N字節對齊”,N即該成員數據類型的長度。如int型成員的自然對界條件爲4字節對齊,而double類型的結構成員的自然對界條件爲8字節對齊。若該成員的起始偏移不位於該成員的“默認自然對界條件”上,則在前一個節面後面添加適當個數的空字節。C編譯器缺省的結構整體的自然對界條件爲:該結構所有成員中要求的最大自然對界條件。若結構體各成員長度之和不爲“結構整體自然對界條件的整數倍,則在最後一個成員後填充空字節。

    那麼可以得到如下的小結:

類型 對齊方式(變量存放的起始地址相對於結構的起始地址的偏移量)
Char    偏移量必須爲sizeof(char)即1的倍數
Short   偏移量必須爲sizeof(short)即2的倍數
int     偏移量必須爲sizeof(int)即4的倍數
float   偏移量必須爲sizeof(float)即4的倍數
double  偏移量必須爲sizeof(double)即8的倍數

   各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節編譯器會自動填充。同時爲了確保結構的大小爲結構的字節邊界數(即該結構中佔用最大空間的類型所佔用的字節數)的倍數,所以在爲最後一個成員變量申請空間後,還會根據需要自動填充空缺的字節,也就是說:結構體的總大小爲結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充字節。對於char數組,字節寬度仍然認爲爲1。

   對於下述的一個結構體,其對齊方式爲:

struct Node1{

    double m1;
    char m2;
    int m3;
};

  對於第一個變量m1,sizeof(double)=8個字節;接下來爲第二個成員m2分配空間,這時下一個可以分配的地址對於結構的起始地址的偏移量爲8,是sizeof(char)的倍數,所以把m2存放在偏移量爲8的地方滿足對齊方式,該成員變量佔用 sizeof(char)=1個字節;接下來爲第三個成員m3分配空間,這時下一個可以分配的地址對於結構的起始地址的偏移量爲9,不是sizeof (int)=4的倍數,爲了滿足對齊方式對偏移量的約束問題,自動填充3個字節(這三個字節沒有放什麼東西),這時下一個可以分配的地址對於結構的起始地址的偏移量爲12,剛好是sizeof(int), 由於8+4+4 = 16恰好是結構體中最大空間類型double(8)的倍數,所以sizeof(Node1) =16.

 

typedef struct{

    char a;

    int b;

    char c;

}Node2;

    成員a佔一個字節,所以a放在了第1位的位置;由於第二個變量b佔4個字節,爲保證起始位置是4(sizeof(b))的倍數,所以需要在a後面填充3個字節,也就是b放在了從第5位到第8位的位置,然後就是c放在了9的位置,此時4+4+1=9。接下來考慮字節邊界數,9並不是最大空間類型int(4)的倍數,應該取大於9且是4的的最小整數12,所以sizeof(Node2) = 12.
typedef struct{

    char a;

    char b;

    int c;

}Node3;

   明顯地:sizeof(Node3) = 8

   對於結構體A中包含結構體B的情況,將結構體A中的結構體成員B中的最寬的數據類型作爲該結構體成員B的數據寬度,同時結構體成員B必須滿足上述對齊的規定。

   要注意在VC中有一個對齊係數的概念,若設置了對齊係數,那麼上述描述的對齊方式,則不適合。

   例如:

1字節對齊(#pragma pack(1))
輸出結果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]
分析過程:
成員數據對齊
#pragma pack(1)
struct test_t {
    int a; 
    char b; 
    short c;
    char d; 
};
#pragma pack()
成員總大小=8;

 

2字節對齊(#pragma pack(2))
輸出結果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]
分析過程:
成員數據對齊
#pragma pack(2)
struct test_t {
    int a; 
    char b; 
    short c;
    char d; 
};
#pragma pack()
成員總大小=9;

 

4字節對齊(#pragma pack(4))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(4)
struct test_t { //按幾對齊, 偏移量爲後邊第一個取模爲零的。
int a; 
char b; 
short c;
char d; 
};
#pragma pack()
成員總大小=9;

 

8字節對齊(#pragma pack(8))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
成員數據對齊
#pragma pack(8)
struct test_t {
int a; 
char b; 
short c;
char d; 
};
#pragma pack()
成員總大小=9;

 

16字節對齊(#pragma pack(16))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(16)
struct test_t {
int a; 
char b; 
short c;
char d; 
};
#pragma pack()
成員總大小=9;

 

至於8字節對齊和16字節對齊,我覺得這兩個例子取得不好,沒有太大的參考意義。


參考:http://hi.baidu.com/x66f/blog/item/b132b7100eb0005cf919b865.html

轉載自: http://blog.sina.com.cn/s/blog_4c8ba35c0100n4fw.h



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