1. 程式人生 > >struct自然邊界上的記憶體對齊

struct自然邊界上的記憶體對齊

    記憶體對齊大多數情況對程式設計師是透明的,是由編譯器自動處理。在C裡面允許我們干預記憶體對齊。而由於記憶體對齊的原因,巧妙的設計結構體也是非常必要的。
   關於記憶體對齊問題,字、雙字和四字在自然邊界上不需要在記憶體中對齊,對字、雙字和四字來說自然邊界分別是奇數地址,可以被2整除地址,和被4整除地址。而資料訪問未對齊的記憶體,處理器需要作兩次記憶體訪問,而對齊的記憶體只作一次訪問;這是原因之一,另外不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。因此為提高程式效能,資料結構尤其是棧應儘可能在自然邊界上對齊。

    一個字起始地址是奇數(當然偶數也行)但卻沒有跨越字邊界,被認為是對齊的。下面來看個例子:
struct TestStruct1
{
       char c1;
       short s;
       char c2;
       int i;
};
   
 如果緊湊排列,c1地址是0,那麼s:為1; c2:為3;i為4,整個結構體大小為8;但很可惜,s為2,c2為4;i為8;而整個結構體大小為12。編譯器將未對齊的s,c2,i成員後移,使之對齊到自然邊界上,從而也讓大小由8變成了12。
如果是這樣的結構:
struct TestStruct2
{
       char c1;
       char c2;
       short s;
 
      int i;
};
    c1地址如果是0,接下來到c2,由於是一個字自然邊界是奇數,所以對齊不需後移,故c2地址為1;到s,雙字自然邊界被2整除,也對齊,不需後移,地址為2;到i,四字自然邊界被4整除,不需後移,地址為4;結構體大小為8。
接下來再看一個例子:
struct TestStruct3
{
       char c1;
       int j;
       char c2;
};
    c1地址如果是0,接下來由於j四字自然邊界被4整除,而沒有對齊,故地址後移3位,為4;到c2,一字自然邊界是奇數不用後移,地址為8,整個結構體應該為9,但很可惜,其實大小為12。
為什麼呢,原因是前面是int,申請的大小為4,以後申請的大小應該都為4。這樣解釋貌似合理。
再看一個例子:
struct TestStruct4
{
       char c1;
       int j;
       char c2;
       char c3
};
    其中c1,j,c2和TestStruct3一樣,到c3,由於c2申請的4個只用了1個,而又有c3自然邊界是奇數,故可以不用後移,地址為9,當然結構體大小也不變為12。
    若結構體變成這種:
struct TestStruct5
{
       char c1;
       int j;
       char c2;
       short i;
};

    則在最後的i,自然邊界為偶數,而又有c2申請了4個只用了1個,3個夠用,所以可以只後移一位,地址為10,結構體大小依舊為12。
再看最後一個例子:
struct TestStruct6
{
       char c1;
       int j;
       short i1;
       char c2;
       short i2;
};
    
    c1如果地址0,那麼j為4,i1為8,char c2為10,接下來的地址為11,由於i2自然邊界是偶數,則後移到12,這時i1申請的4個地址全部用完,這時i2申請的大小應該為多少呢,實驗證明:還是申請4。結構體大小為:16。
    以上看來,結構體的設計技巧對記憶體空間的管理還是很重要的,
    而在C中允許干涉記憶體對齊的“家庭糾紛“的方法就是用預處理命令#pragma,在所有預處理命令中,它可能也是最複雜的,它的作用是設定編譯器的狀態或是指示編譯器完成一些特定的動作,關於這個命令的用法,我還沒詳細瞭解多少。
    這裡用到的是:#pragma pack(n) 和#pragma();前者是編譯器按n個位元組對齊,後者是取消編譯器的自定義位元組對齊方式,一般需要對一個物件自定義,則將其寫在兩者之間即可。

    #SinaEditor_Temp_FontName它的規則是:成員自身的對齊方式和n相比,位元組對齊是以較小的為準。

    本文轉載自關於記憶體對齊