天天看點

什麼是記憶體對齊

考慮下面的結構:

         struct foo

         {

           char c1;

           short s;

           char c2;

           int i;

          };

    假設這個結構的成員在記憶體中是緊湊排列的,假設c1的位址是0,那麼s的位址就應該是1,c2的位址就是3,i的位址就是4。也就是

    c1 00000000, s 00000001, c2 00000003, i 00000004。

    可是,我們在Visual c/c++ 6中寫一個簡單的程式:

         struct foo a;

    printf("c1 %p, s %p, c2 %p, i %p\n",

        (unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,

        (unsigned int)(void*)&a.s - (unsigned int)(void*)&a,

        (unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,

        (unsigned int)(void*)&a.i - (unsigned int)(void*)&a);

    運作,輸出:

         c1 00000000, s 00000002, c2 00000004, i 00000008。

    為什麼會這樣?這就是記憶體對齊而導緻的問題。

為什麼會有記憶體對齊

    以下内容節選自《Intel Architecture 32 Manual》。

    字,雙字,和四字在自然邊界上不需要在記憶體中對齊。(對字,雙字,和四字來說,自然邊界分别是偶數位址,可以被4整除的位址,和可以被8整除的位址。)

    無論如何,為了提高程式的性能,資料結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了通路未對齊的記憶體,處理器需要作兩次記憶體通路;然而,對齊的記憶體通路僅需要一次通路。

    一個字或雙字操作數跨越了4位元組邊界,或者一個四字操作數跨越了8位元組邊界,被認為是未對齊的,進而需要兩次總線周期來通路記憶體。一個字起始位址是奇數但卻沒有跨越字邊界被認為是對齊的,能夠在一個總線周期中被通路。

    某些操作雙四字的指令需要記憶體操作數在自然邊界上對齊。如果操作數沒有對齊,這些指令将會産生一個通用保護異常(#GP)。雙四字的自然邊界是能夠被16整除的位址。其他的操作雙四字的指令允許未對齊的通路(不會産生通用保護異常),然而,需要額外的記憶體總線周期來通路記憶體中未對齊的資料。

編譯器對記憶體對齊的處理

    預設情況下,c/c++編譯器預設将結構、棧中的成員資料進行記憶體對齊。是以,上面的程式輸出就變成了:

c1 00000000, s 00000002, c2 00000004, i 00000008。

編譯器将未對齊的成員向後移,将每一個都成員對齊到自然邊界上,進而也導緻了整個結構的尺寸變大。盡管會犧牲一點空間(成員之間有空洞),但提高了性能。

也正是這個原因,我們不可以斷言sizeof(foo) == 8。在這個例子中,sizeof(foo) == 12。

如何避免記憶體對齊的影響

    那麼,能不能既達到提高性能的目的,又能節約一點空間呢?有一點小技巧可以使用。比如我們可以将上面的結構改成:

struct bar

{

    char c1;

    char c2;

    short s;

    int i;

};

    這樣一來,每個成員都對齊在其自然邊界上,進而避免了編譯器自動對齊。在這個例子中,sizeof(bar) == 8。

    這個技巧有一個重要的作用,尤其是這個結構作為API的一部分提供給第三方開發使用的時候。第三方開發者可能将編譯器的預設對齊選項改變,進而造成這個結構在你的發行的DLL中使用某種對齊方式,而在第三方開發者哪裡卻使用另外一種對齊方式。這将會導緻重大問題。

    比如,foo結構,我們的DLL使用預設對齊選項,對齊為

c1 00000000, s 00000002, c2 00000004, i 00000008,同時sizeof(foo) == 12。

而第三方将對齊選項關閉,導緻

    c1 00000000, s 00000001, c2 00000003, i 00000004,同時sizeof(foo) == 8。

如何使用c/c++中的對齊選項

    vc6中的編譯選項有 /Zp[1|2|4|8|16] ,/Zp1表示以1位元組邊界對齊,相應的,/Zpn表示以n位元組邊界對齊。n位元組邊界對齊的意思是說,一個成員的位址必須安排在成員的尺寸的整數倍位址上或者是n的整數倍位址上,取它們中的最小值。也就是:

    min ( sizeof ( member ),  n)

    實際上,1位元組邊界對齊也就表示了結構成員之間沒有空洞。

    /Zpn選項是應用于整個工程的,影響所有的參與編譯的結構。

    要使用這個選項,可以在vc6中打開工程屬性頁,c/c++頁,選擇Code Generation分類,在Struct member alignment可以選擇。

    要專門針對某些結構定義使用對齊選項,可以使用#pragma pack編譯指令。指令文法如下:

#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n  )

    意義和/Zpn選項相同。比如:

#pragma pack(1)

struct foo_pack

#pragma pack()

棧記憶體對齊

    我們可以觀察到,在vc6中棧的對齊方式不受結構成員對齊選項的影響。(本來就是兩碼事)。它總是保持對齊,而且對齊在4位元組邊界上。

驗證代碼

#include <stdio.h>

struct foo

int main(int argc, char* argv[])

    struct foo a;

    struct bar b;

    struct foo_pack p;

    printf("stack c1 %p, s %p, c2 %p, i %p\n",

        (unsigned int)(void*)&c1 - (unsigned int)(void*)&i,

        (unsigned int)(void*)&s - (unsigned int)(void*)&i,

        (unsigned int)(void*)&c2 - (unsigned int)(void*)&i,

        (unsigned int)(void*)&i - (unsigned int)(void*)&i);

    printf("struct foo c1 %p, s %p, c2 %p, i %p\n",

    printf("struct bar c1 %p, c2 %p, s %p, i %p\n",

        (unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b,

        (unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b,

        (unsigned int)(void*)&b.s - (unsigned int)(void*)&b,

        (unsigned int)(void*)&b.i - (unsigned int)(void*)&b);

    printf("struct foo_pack c1 %p, s %p, c2 %p, i %p\n",

        (unsigned int)(void*)&p.c1 - (unsigned int)(void*)&p,

        (unsigned int)(void*)&p.s - (unsigned int)(void*)&p,

        (unsigned int)(void*)&p.c2 - (unsigned int)(void*)&p,

        (unsigned int)(void*)&p.i - (unsigned int)(void*)&p);

    printf("sizeof foo is %d\n", sizeof(foo));

    printf("sizeof bar is %d\n", sizeof(bar));

    printf("sizeof foo_pack is %d\n", sizeof(foo_pack));

    return 0;

}

 vc6中的編譯選項有 /Zp[1|2|4|8|16] ,/Zp1表示以1位元組邊界對齊,相應的,/Zpn表示以n位元組邊界對齊。

n位元組邊界對齊的意思是說,一個成員的位址必須安排在成員的尺寸的整數倍位址上或者是n的整數倍位址上,取它們中的最小值。也就是:

    min ( sizeof ( member ),  n)

    實際上,1位元組邊界對齊也就表示了結構成員之間沒有空洞。

/*1位元組邊界對齊表示結構成員之間沒有空隙,個個成員變量在記憶體中是緊密排列的*/

    /Zpn選項是應用于整個工程的,影響所有的參與編譯的結構。

專注于企業資訊化,最近對股票資料分析較為感興趣,可免費分享股票個股主力資金實時變化趨勢分析工具,股票交流QQ群:457394862

本文轉自滄海-重慶部落格園部落格,原文連結:http://www.cnblogs.com/omygod/archive/2006/11/15/560693.html,如需轉載請自行聯系原作者

繼續閱讀