C++ struct結構體記憶體對齊

MElephant發表於2022-03-22

 

•小試牛刀

  我們自定義兩個結構體 A 和 B:

struct A
{
    char c1;
    char c2;
    int i;
    double d;
};
struct B
{
    char c1;
    int i;
    char c2;
    double d;
};

  通過定義我們可以看出,結構體 A 和 B 擁有相同的成員,只不過在排列順序上有所不同;

  眾所周知,char 型別佔 1 個位元組,int 型別佔 4 個位元組,double 型別佔 8 個位元組;

  那麼,這兩個結構體所佔記憶體空間大小為多少呢?佔用的空間是否相同?

  空口無憑,讓我們通過編譯器告訴我們答案(我使用的是 VS2022,X86)。

  在 main() 函式中輸出如下語句:

int main()
{
    printf("結構體A所佔記憶體大小為:%d\n", sizeof(A));
    printf("結構體B所佔記憶體大小為:%d\n", sizeof(B));

    return 0;
}

  執行之前,先盲猜一個結果:

 sizeof(A) = sizeof(B) = sizeof(c1)+sizeof(c2)+sizeof(i)+sizeof(d) = 1+1+4+8 = 14

  到底對不對呢?讓我們來看看執行結果:

  amazing~~

  竟然一個都沒猜對,這究竟是怎麼回事呢?

  下面開始進入今天的主題——struct 記憶體對齊。

•記憶體對齊

  一種提高記憶體訪問速度的策略,CPU 在訪問未對齊的記憶體可能需要經過兩次的記憶體訪問,而經過記憶體對齊一次就可以了。

  假定現在有一個 32 位處理器,那這個處理器一次性讀取或寫入都是四位元組。

  假設現在有一個 32 位處理器要讀取一個 int 型別的變數,在記憶體對齊的情況下,處理器是這樣進行讀取的:

 

C++ struct結構體記憶體對齊

  那如果資料儲存沒有按照記憶體對齊的方式進行的話,處理器就會這樣進行讀取:

C++ struct結構體記憶體對齊

  對比記憶體對齊和記憶體沒有對齊兩種情況我們可以明顯地看到,在記憶體對齊的情況下,取得這個 int型 變數只需要經過一次定址(0~3);

  但在記憶體沒有對齊的情況下,取得這個 int型 變數需要經過兩次的定址(0~3 和 4~7),然後再合併資料。

  通過上述的分析,我們可以知道記憶體對齊能夠提升效能,這也是我們要進行記憶體對齊的原因之一。

•記憶體對齊的原則

  1. 對於結構體的各個成員,除了第一個成員的偏移量為 0 外,其餘成員的偏移量是 其實際長度 的整數倍,如果不是,則在前一個成員後面補充位元組。
  2. 結構體內所有資料成員各自記憶體對齊後,結構體本身還要進行一次記憶體對齊,保證整個結構體佔用記憶體大小是結構體內最大資料成員的最小整數倍。
  3. 如程式中有  #pragma pack(n) 預編譯指令,則所有成員對齊以 n位元組 為準(即偏移量是n的整數倍),不再考慮當前型別以及最大結構體內型別。

  下面通過樣例來分享一下我的見解,為方便理解,宣告如下:

  • 定義的結構體包含 char , short , int , double型別各一個,並通過不同的組合構造出不同的結構體 Test01 , Test02 , Test03 , Test04
  • 記憶體地址的編號設定為 0~24
  • char 型別佔1 個 位元組,並用橙色填充
  • short 型別佔 2個 位元組,並用黃色填充
  • int 型別佔 4個 位元組,並用綠色填充
  • double 型別佔 8個 位元組,並用藍色填充
  • 補充位元組用黑色填充

Test01

struct Test01
{
    char c;
    short s;
    int i;
    double d;
}t1;

  記憶體分佈情況:

C++ struct結構體記憶體對齊

  • 第一個成員 c 的偏移量為 0,所以成員 c 的記憶體空間的首地址為 0
  • 第二個成員 s 的記憶體空間的首地址為 2 號地址,偏移量為 2 - 0 = 2
  • 第三個成員 i 的記憶體空間的首地址為 4 號地址,偏移量為 4 - 0 = 4
  • 第三個成員 d 的記憶體空間的首地址為 8 號地址,偏移量為 8 - 0 = 8
  • Test01 所佔記憶體大小為 16 個位元組

  讓我們通過輸出來驗證一下:

void showTest01()
{
    printf("Test01所佔記憶體大小:%d\n",sizeof(Test01));
    //並按照宣告順序輸出 Test01 中的成員變數地址對應的十六進位制
    printf("%p\n", &t1.c);
    printf("%p\n", &t1.s);
    printf("%p\n", &t1.i);
    printf("%p\n", &t1.d);
}

  輸出結果:

  我們將輸出的十六進位制地址轉化為十進位制:

 00209400 -> 2135040

 00209402 -> 2135042

 00209404 -> 2135044

 00209408 -> 2135048

  • 以第一個成員 c 的起始地址為起點
  • 第二個成員 s 的偏移量為 2
  • 第三個成員 i 的偏移量為 4
  • 第四個成員 d 的偏移量為 8
  • 所佔記憶體大小為 16

  驗證成功!

Test02

  調換一下成員順序,再次測試:

struct Test02
{
    char c;
    double d;
    int i;
    short s;
}t2;

  記憶體分佈情況:

C++ struct結構體記憶體對齊

  • 第一個成員 c 的偏移量為 0,所以成員 c 的記憶體空間的首地址為 0
  • 第二個成員 d 的記憶體空間的首地址為 8 號地址,偏移量為 8 - 0 = 8(double 型別的整倍數)
  • 第三個成員 i 的記憶體空間的首地址為 16 號地址,偏移量為 16 - 0 = 16(int 型別的整倍數)
  • 第三個成員 s 的記憶體空間的首地址為 20 號地址,偏移量為 20 - 0 = 20(short 型別的整倍數)
  • Test02 所佔記憶體大小為 24 個位元組(結構體佔用記憶體大小是結構體內最大資料成員 double 的最小整數倍:24 / 8 = 4)

  接著通過輸出來驗證一下:

  我們將輸出的十六進位制地址轉化為十進位制:

 007C9410 -> 8164368

 007C9418 -> 8164376

 007C9420 -> 8164384

 007C9424 -> 8164388

  • 以第一個成員 c 的起始地址為起點
  • 第二個成員 d 的偏移量為  8164376 - 8164368 = 8 
  • 第三個成員 i 的偏移量為  8164384 - 8164368 = 16 
  • 第四個成員 d 的偏移量為  8164388 - 8164368 = 20 
  • 所佔記憶體大小為 24

  驗證成功!

Test03 & Test04

struct Test03
{
    short s;
    double d;
    char c;
    int i;
}t3;
struct Test04
{
    double d;
    char c;
    int i;
    short s;
}t4;

  記憶體分佈情況:

C++ struct結構體記憶體對齊

  可自行輸出驗證!!!

•總結

  通過自行模擬,再回過頭看看記憶體對齊的原則,是不是有種恍然大明白的感覺~

  通過模擬上述不同情況,你會發現同種型別的成員變數通過不同的組合,所佔用的總記憶體是不相同的;

  那麼,關於結構體內成員定義的順序應該遵循這樣一個原則:按照長度遞增的順序依次定義各個成員。

•宣告

參考資料

  1. struct 記憶體對齊 | CSDN

  2. struct結構體記憶體對齊解析 | CSDN

  3. 線上進位制轉換

 

相關文章