DEV Community

Cover image for 這兩個 C++ 結構為什麼大小不一樣?
codemee
codemee

Posted on • Edited on

1

這兩個 C++ 結構為什麼大小不一樣?

C++ 有些細節雖然不知道可能不會影響你的程式碼正確執行, 不過卻可能影響你的程式耗用的記憶體。舉例來說, 底下這個程式定義了兩個幾乎相同的結構, 只是成員的順序不一樣, 不過如果顯示這兩個結構的大小, 就會發現 data2 要比 data1 多耗費了 4 個位元組:

#include <iostream>
using namespace std;

struct data1 {
    char c1;
    char c2;
    int i1;
    int i2; 
};

struct data2 {
    char c1;
    int i1;
    char c2;
    int i2; 
};

int main() {
    cout << "sizeof(data1) = " << sizeof(data1) << "\n";
    cout << "sizeof(data2) = " << sizeof(data2) << "\n";
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

實際執行結果如下:

sizeof(data1) = 12
sizeof(data2) = 16
Enter fullscreen mode Exit fullscreen mode

要理解為什麼, 就必須理解 C++ 中個別型態的位址對齊 (alignment)

位址對齊 (alignment)

在 C++ 中, 為了存取記憶體效率的關係, 不同型態的物件必須放在特定數值 (通常是 2 的冪) 倍數的位址上, 這就稱為位址對齊。你可以透過 alignof 運算子得知指定型態物件的位置對齊數值:

#include <iostream>
using namespace std;

int main()
{
    cout << "alignof(int): " << alignof(int) << endl; // 4
    cout << "alignof(double): " << alignof(double) << endl; // 8
    cout << "alignof(char): " << alignof(char) << endl; // 1
    cout << "alignof(float): " << alignof(float) << endl; // 4
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

輸出結果如下:

alignof(int): 4
alignof(double): 8
alignof(char): 1
alignof(float): 4
Enter fullscreen mode Exit fullscreen mode

int 為例, 如果現在可用的記憶體位址從 1017 開始, 若要配置一個 int 變數, 因為它的位址對齊數值是 4, 就必須往後到 4 的倍數, 也就是位址 1020 開始才能配置。反觀 char, 它的位址對齊數值是 1, 所以如果是要配置 char 變數, 就可以直接從 1017 配置, 不需要移動。

位址對齊照成的補白 (padding)

剛剛看到的 int 變數的例子, 就會浪費 1017 到 1019 之間的 3 個位元組, 這稱為補白 (padding), 也就是照成文章一開始兩個成員相同但順序不同的結構大小不同的原因。

data1 為例:

struct data1 {
    char c1;
    char c2; // 這裡需要補白 2 個位元組
    int i1;
    int i2; 
};
Enter fullscreen mode Exit fullscreen mode

因為 char 的位址對齊數值為 1, 所以前兩個 char 成員可以相鄰配置, 但是 int 的位址對齊數值為 4, 所以在這兩個 char 成員之後要 2 個位元組的補白, 變成佔用 4 個位元組, 加上後面兩個 int 個別佔 4 個位元組, 所以共佔用 12 個位元組。

以下則是 data2

struct data2 {
    char c1; // 這裡需要補白 3 個位元組
    int i1;
    char c2; // 這裡需要補白 3 個位元組
    int i2; 
};
Enter fullscreen mode Exit fullscreen mode

因為 charint 成員交錯, 所以每個 char 成員之後需要 3 個位元組的補白, 才能讓 int 成員位址對齊 4 的倍數, 導致兩個 char 成員都佔用了 4 個位元組, 加上兩個 int 成員, 所以總共佔用了 16 個位元組。

瞭解這個差異後, 你就可以在必要的時候, 調整成員的順序, 減少記憶體的用量了。

讓整個結構也對齊

你可能會想說, 如果把兩個 char 成員都放在最後, 是不是就可以不用補白, 再省 2 個位元組的空間呢?以下是測試結果:

#include <iostream>
using namespace std;

struct data3 {
    int i1;
    int i2; 
    char c1;
    char c2;
};

int main() {
    cout << "sizeof(data3) = " << sizeof(data3) << "\n";
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

結果如下:

sizeof(data3) = 12
Enter fullscreen mode Exit fullscreen mode

咦?現在每個成員都各自滿足對齊要求, 為什麼還是多了 2 個位元組的補白?這是因為結構還會在最尾端依照對齊需求最大的成員為準加上必要的補白。

你可以這樣想, 如果要宣告一個陣列 data3 ary[3], 就必須連續配置 3 個 data3 的空間, 在每個 data3 結尾處必須加上 2 個位元組的補白, 才能讓下一個 data3 內的成員正確對齊, 因此實際上 data3 的大小就是加上結尾處的補白, 仍然是 12 個位元組了。

這樣一來, 如果你要計算陣列中的元素數量, 就不會計算錯誤, 否則陣列的大小包含有補白的位元組, 但是個別元素的大小卻沒有包含結尾的補白, 就無法正確算出元素的數量了。

Image of Datadog

Master Mobile Monitoring for iOS Apps

Monitor your app’s health with real-time insights into crash-free rates, start times, and more. Optimize performance and prevent user churn by addressing critical issues like app hangs, and ANRs. Learn how to keep your iOS app running smoothly across all devices by downloading this eBook.

Get The eBook

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs