結構體和類的內存對齊原則-這一次弄清楚了對齊的本質規則

內存對齊計算可謂是筆試題的必考題,可是如何按照計算原則算出正確答案一開始也不是很容易的事,因此專門經過例子來複習下關於結構體內存對齊的計算問題。(編譯環境爲vs2015)

對齊原則:

原則1:數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,之後每一個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。數據結構

原則2:結構(或聯合)的總體對齊規則:在數據成員完成各自對齊以後,結構(或聯合)自己也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。性能

原則3:結構體做爲成員:若是一個結構裏有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲。大數據

默認對齊值:

Linux 默認#pragma pack(4)spa

window 默認#pragma pack(8)操作系統

注:能夠經過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是指定的「對齊係數」。code

例一:一字節對齊

第一步: 成員數據對齊

#pragma pack(1)
struct AA {
    int a;   //長度4 > 1 按1對齊;偏移量爲0;存放位置區間[0,3]
    char b;  //長度1 = 1 按1對齊;偏移量爲4;存放位置區間[4]
    short c; //長度2 > 1 按1對齊;偏移量爲5;存放位置區間[5,6]
    char d;  //長度1 = 1 按1對齊;偏移量爲6;存放位置區間[7]
    //總體存放在[0~7]位置區間中,共八個字節。
};
#pragma pack()

第二步: 總體對齊

總體對齊係數 = min((max(int,short,char), 1) = 1,因此不須要再進行總體對齊。總體大小就爲8。

圖示以下:

這裏寫圖片描述

例二:二字節對齊

第一步: 成員數據對齊

#pragma pack(2)
struct AA {
    int a;   //長度4 > 2 按2對齊;偏移量爲0;存放位置區間[0,3]
    char b;  //長度1 < 2 按1對齊;偏移量爲4;存放位置區間[4]
    short c; //長度2 = 2 按2對齊;偏移量要提高到2的倍數6;存放位置區間[6,7]
    char d;  //長度1 < 2 按1對齊;偏移量爲7;存放位置區間[8];共九個字節
};
#pragma pack()

第二步: 總體對齊

總體對齊係數 = min((max(int,short,char), 2) = 2,將9提高到2的倍數,則爲10.因此最終結果爲10個字節。

圖示以下:(X爲補齊部分)

這裏寫圖片描述

例三:四字節對齊

第一步: 成員數據對齊

#pragma pack(4)
struct AA {
    int a;   //長度4 = 4 按4對齊;偏移量爲0;存放位置區間[0,3]
    char b;  //長度1 < 4 按1對齊;偏移量爲4;存放位置區間[4]
    short c; //長度2 < 4 按2對齊;偏移量要提高到2的倍數6;存放位置區間[6,7]
    char d;  //長度1 < 4 按1對齊;偏移量爲7;存放位置區間[8];總大小爲9
};
#pragma pack()

第二步: 總體對齊

總體對齊係數 = min((max(int,short,char), 4) = 4,將9提高到4的倍數,則爲12.因此最終結果爲12個字節。

圖示以下:(X爲補齊部分)

這裏寫圖片描述

例三:八字節對齊

第一步: 成員數據對齊

#pragma pack(8)
struct AA {
    int a;   //長度4 < 8 按4對齊;偏移量爲0;存放位置區間[0,3]
    char b;  //長度1 < 8 按1對齊;偏移量爲4;存放位置區間[4]
    short c; //長度2 < 8 按2對齊;偏移量要提高到2的倍數6;存放位置區間[6,7]
    char d;  //長度1 < 8 按1對齊;偏移量爲7;存放位置區間[8],總大小爲9
};
#pragma pack()

第二步: 總體對齊

總體對齊係數 = min((max(int,short,char), 8) = 4,將9提高到4的倍數,則爲12.因此最終結果爲12個字節。圖示如上。

注:能夠經過stddef.h庫中的offsetof宏來查看對應結構體元素的偏移量。

例四:結構體中包含結構體的運算

總體計算過程以下

struct EE
{
    int a;      //長度4 < 8 按4對齊;偏移量爲0;存放位置區間[0,3]
    char b;     //長度1 < 8 按1對齊;偏移量爲4;存放位置區間[4]
    short c;    //長度2 < 8 按2對齊;偏移量由5提高到6;存放位置區間[6,7]
    //結構體內部最大元素爲int,因爲偏移量爲8恰好是4的整數倍,因此從8開始存放接下來的struct FF
    struct FF
    {
        int a1;     //長度4 < 8 按4對齊;偏移量爲8;存放位置區間[8,11]
        char b1;    //長度1 < 8 按1對齊;偏移量爲12;存放位置區間[12]
        short c1;   //長度2 < 8 按2對齊;偏移量爲13,提高到2的倍數14;存放位置區間[14,15]
        char d1;    //長度1 < 8 按1對齊;偏移量爲16;存放位置區間[16]
    };
    //總體對齊係數 = min((max(int,short,char), 8) = 4,將內存大小由17補齊到4的整數倍20
    char d;         //長度1 < 8 按1對齊;偏移量爲21;存放位置區間[21]
    //總體對齊係數 = min((max(int,short,char), 8) = 4,將內存大小由21補齊到4的整數倍24
};

圖示以下:

這裏寫圖片描述

例五:再來一個嵌套結構體的計算

總體計算過程以下

struct B {
    char e[2];      //長度1 < 8 按2對齊;偏移量爲0;存放位置區間[0,1]
    short h;        //長度2 < 8 按2對齊;偏移量爲2;存放位置區間[2,3]
    //結構體內部最大元素爲double,偏移量爲4,提高到8,因此從8開始存放接下來的struct A
    struct A {
        int a;      //長度4 < 8 按4對齊;偏移量爲8;存放位置區間[8,11]
        double b;   //長度8 = 8 按8對齊;偏移量爲12,提高到16;存放位置區間16,23]
        float c;    //長度4 < 8,按4對齊;偏移量爲24,存放位置區間[24,27]
    };
    //總體對齊係數 = min((max(int,double,float), 8) = 8,將內存大小由28補齊到8的整數倍32
};

圖示以下:

這裏寫圖片描述

小結:當#pragma pack的n值等於或超過全部數據成員長度的時候,這個n值的大小將不產生任何效果。

會了關於結構體內存大小的計算,但是爲何系統要對於結構體數據進行內存對齊呢,很明顯所佔用的空間大小要更多。緣由可概括以下:

一、平臺緣由(移植緣由):不是全部的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,不然拋出硬件異常。圖片

二、性能緣由:數據結構(尤爲是棧)應該儘量地在天然邊界上對齊。緣由在於,爲了訪問未對齊的內存,處理器須要做兩次內存訪問;而對齊的內存訪問僅須要一次訪問。內存

更簡單的說明下:如圖io

這裏寫圖片描述

首先,cpu的訪問粒度爲4,也就是一次性能夠讀取內存中的四個字節內容;當咱們不採用內存對齊策略,若是須要訪問A中的b元素,cpu須要先取出0~3四個字節的內容,發現沒有讀取完,還須要再次讀取,一共須要進行兩次訪問內存的操做;而有了內存對齊,參考左圖,可一次性取出4~7四個字節的元素也便是b,這樣就只須要進行一次訪問內存的操做。因此操做系統這樣作的緣由也就是所謂的拿空間換時間,提升效率。編譯

建議:雖然操做系統會浪費空間來完成內存對齊,可是咱們有了上面的知識能夠經過按照數據類型來調整結構體內部的數據的前後順序來儘可能減小內存的消耗;例如咱們將下面結構體A中的順序調整爲B,sizeof(A)的結果爲12,而sizeof(B)的結果就是8:

struct A
{
    char a;
    int b;
    char c;
};

struct B
{
    char a;
    char c;
    int b;
};
相關文章
相關標籤/搜索