結構體內存分析

1. 什么是內存對齊

看下面的小程序,理論上,int4 byte,char占一個1 byte,那么將它們放到一個結構體中應該占4+1=5byte,但是實際上,通過運行程序得到的結果是8 byte,這就是內存對齊所導致的。

struct Struct {
    int a;  // 4
    char b; // 1
}struct4;

NSLog(@"%lu",sizeof(struct4)); // 輸出為 8

計算機中內存空間都是按照 byte 劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但是實際的計算機系統(tǒng)對基本類型數(shù)據(jù)在內存中存放的位置有限制,它們會要求這些數(shù)據(jù)的首地址的值是某個數(shù)k(通常它為48)的倍數(shù),這就是所謂的內存對齊。

2. 為什么要內存對齊

平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。

計算機的處理器是以一定大小的塊來進行讀取的,這作為我們的前提條件。

對齊跟數(shù)據(jù)在內存中的位置有關。如果一個變量的內存地址剛好位于它本身長度的整數(shù)倍,他就被稱做自然對齊。例如一個整型變量(占4字節(jié))的地址為0x00000008,那它就是自然對齊的。

現(xiàn)在假設一個整型變量(4字節(jié))不是自然對齊的,它的起始地址落在0x00000002(圖中藍色區(qū)域),處理器想要訪問它的值,按照4字節(jié)的塊進行讀取,從圖中的0x00起讀,讀取4字節(jié)大小,讀到0x03

image.png

這樣的一次讀取之后,我們并不能取到我們要訪問的整型數(shù)據(jù),緊接著處理器會繼續(xù)再往下讀,偏移4個字節(jié),從0x04開始,讀到0x07

image.png

到這一步,處理器才能讀取到了我們需要訪問的內存數(shù)據(jù),當然這中間還存在剔除與合并的過程。在上面的例子中,要讀取兩次才能獲取到我們想要的數(shù)據(jù)。

那么如果是內存對齊的呢?

image.png

由上圖可知,只要讀取一次就能獲取到相應的數(shù)據(jù)。

因此可以得出,內存是否對齊,會影響到數(shù)據(jù)的讀取效率。另外不同的內存存取粒度對同一任務也有不同影響,這里我們不過多的討論。

3.內存對齊原則

  1. 數(shù)據(jù)成員對?規(guī)則:結構(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方(即首地址的位置),以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組,結構體等)的整數(shù)倍開始(比如int4字節(jié),則要從4的整數(shù)倍地址開始存儲。
  2. 結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從其內部最大元素大小的整數(shù)倍地址開始存儲.(struct a里存有struct b,b里有char、int 、double等元素,那b應該從8的整數(shù)倍開始存儲.)
  3. 收尾工作:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員的整數(shù)倍,不足的要補?。

按我自己的理解來看:

  • 對于規(guī)則1 每個數(shù)據(jù)成員的起始位置,都是自身大小的整數(shù)倍。
  • 對于規(guī)則2 對于結構體做為成員變量,起始位置要根據(jù)自身成員變量最大的元素來確定。

下面我們來看一個簡單的例子:

struct TestStruct {
    double a;       // 8    
    char b;         // 1    
    int c;          // 4   
    short d;        // 2    
}struct1;

double的大小為8個字節(jié),按照規(guī)則1, a成員變量將會占據(jù)前首地址開始的 8個字節(jié)。也就是0x000x07的地址。
char的大小為1,按照規(guī)則1,內存中的第9為是1的倍數(shù),占據(jù)一個字節(jié)0x08,單數(shù)成員變量c,為int類型,大小為4,內存中的第10為并不是4的倍數(shù),所以并不能滿足規(guī)則1。因此,成員變量b要補3個位置,即占據(jù)0x080x11的地址單元,成員變量c0x120x15開始占據(jù)4個內存單元。同理,按照規(guī)則1可以得出成員變量d占據(jù)0x160x08的內存單元,總共占據(jù)18個字節(jié)。最后,我們進行收尾工作:結構體的總大小,必須是其內部最大成員的整數(shù)倍。內部最大成員為double 8個字節(jié),可以計算出結構的總大小為24

最后我們用代碼來驗證下:

image.png

如果結構體中存在結構體成員變量會怎樣?

struct TestStruct2 {
    double a; // 8
    int b;    // 4
    char c;   // 1
    short d;  // 2
    int e;    // 4
    struct TestStruct1 str;
}struct2;

按照規(guī)則1,可以得出前5個變量占據(jù)24個內存單元,對于結構體成員str,其內部最大成員為double 8個字節(jié),而248的倍數(shù),符合規(guī)則1, str占據(jù)后面24個字節(jié)單元。此時,結構體總共占據(jù)48個內存單元,按照規(guī)則3 TestStruct2的最大成員是 TestStruct1 str 24個字節(jié),符合規(guī)則。
下面我么用代碼驗證下結果

image.png

4. 總結

其實,內存對齊就是定制了一套規(guī)則,以合理的利用內存空間并提高內存訪問效率。 編譯器通過適當增加padding,使每個成員的訪問都在一個指令里完成,而不需要多次訪問再拼接。是一個以空間換時間的過程。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容