Structure Padding 和 Packaging in C

您好,未來的電腦魔法師們!今天,我們將踏上一段令人興奮的旅程,進入 C 語言的世界,特別是探討結構填充(structure padding)和打包(packing)的概念。別擔心這些術語現在聽起來像是在胡言亂語——到了這個教學的結尾,你將能夠像專業人士一樣向你的朋友們解釋它們!

C - Structure Padding and Packing

C 語言中的結構填充是什麼?

想像你正在為一次旅行打包行李箱。你希望把每樣東西都整齊地放進去,但有時候會在物品之間留下一些奇怪的空隙。在 C 語言中,結構填充就像是你行李箱中的那些空隙。

當我們在 C 語言中創建一個結構時,編譯器有時會在結構成員之間添加額外的字節。這被稱為填充(padding)。但為什麼它會這樣做呢?這一切都是關於效率,以及確保我們的計算機能夠快速地讀取數據。

讓我們看一個簡單的例子:

struct Example {
char c;
int i;
char d;
};

你可能會認為這個結構會佔用 6 個字節(每個 char 佔 1 個字節,int 佔 4 個字節)。但在現實中,它通常會佔用 12 個字節!讓我們來分析一下:

  1. char c 佔用 1 個字節。
  2. 為了對齊 int i,編譯器在 c 之後添加了 3 個字節的填充。
  3. int i 佔用 4 個字節。
  4. char d 佔用 1 個字節。
  5. 為了使整個結構的大小是 4 的倍數(為了對齊),在最後添加了 3 個字節。

所以,1 + 3 + 4 + 1 + 3 = 12 個字節總共。

通過例子理解結構填充

讓我們深入研究更多例子,以真正掌握這個概念。

示例 1:不同的順序,不同的填充

struct StructA {
char c;
int i;
char d;
};

struct StructB {
int i;
char c;
char d;
};

在這個例子中,StructA 通常會佔用 12 個字節,正如我們之前所見。但 StructB 只會佔用 8 個字節!佈局會是這樣:

  1. int i:4 個字節
  2. char c:1 個字節
  3. char d:1 個字節
  4. 最後有 2 個字節的填充

這向我們顯示,結構中成員的順序會影響其大小,這是因為填充。

示例 2:使用 sizeof() 來檢查結構大小

#include <stdio.h>

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

struct PackedStruct {
char a;
char c;
int b;
} __attribute__((packed));

int main() {
printf("PaddedStruct 的大小:%lu\n", sizeof(struct PaddedStruct));
printf("PackedStruct 的大小:%lu\n", sizeof(struct PackedStruct));
return 0;
}

這段代碼會輸出:

PaddedStruct 的大小:12
PackedStruct 的大小:6

sizeof() 函數是我們的好夥伴,幫助我們看到我們結構的實際大小。

C 語言中的結構打包是什麼?

現在我們了解了填充,讓我們來谈谈它的對應概念:打包。結構打包就像是在玩俄羅斯方塊遊戲——你試圖把所有數據都擠得越緊湊越好,不留下任何間隙。

當我們打包一個結構時,我們是在告訴編譯器:“嘿,不要添加任何額外的填充。我想要這些數據盡可能地緊湊。”

通過例子理解結構打包

讓我們看一些例子,實際看看打包是如何工作的。

示例 1:使用打包屬性

struct PackedExample {
char c;
int i;
char d;
} __attribute__((packed));

通過添加 __attribute__((packed)),我們告訴編譯器緊湊地打包這個結構。現在,sizeof(struct PackedExample) 將返回 6 而不是 12。

示例 2:比較打包和未打包的結構

#include <stdio.h>

struct UnpackedStruct {
char a;
int b;
short c;
};

struct PackedStruct {
char a;
int b;
short c;
} __attribute__((packed));

int main() {
printf("UnpackedStruct 的大小:%lu\n", sizeof(struct UnpackedStruct));
printf("PackedStruct 的大小:%lu\n", sizeof(struct PackedStruct));
return 0;
}

這將輸出:

UnpackedStruct 的大小:12
PackedStruct 的大小:7

未打包的結構有填充,而打包的則沒有。

示例 3:打包結構的潛在問題

雖然打包可以節省內存,但它有时會導致較慢的訪問時間,或者在某些系統上甚至會導致錯誤。以下是一個可能導致問題的例子:

#include <stdio.h>

struct PackedStruct {
char a;
int b;
} __attribute__((packed));

int main() {
struct PackedStruct ps;
ps.a = 'A';
ps.b = 12345;

int *ptr = &ps.b;
printf("b 的值:%d\n", *ptr);

return 0;
}

在某些系統上,這可能會正常工作。在其他的系統上,它可能會引起對齊錯誤,因為 ps.b 沒有對齊到 4 字節的邊界。

結論

理解結構填充和打包對於編寫高效的 C 代碼至關重要,尤其是在處理嵌入式系統或內存優化非常重要的情況下。記住,填充是關於性能,而打包是關於節省空間。像編程中的許多事情一樣,這一切都取決於為你的特定需求找到正確的平衡。

這裡是一個我們討論過的方法的快速參考表:

方法 描述 示例
默認填充 編譯器自動添加填充 struct Example { char c; int i; };
使用屬性打包 強制結構打包 struct PackedExample { char c; int i; } __attribute__((packed));
使用 sizeof() 檢查結構的實際大小 sizeof(struct Example)

繼續實驗這些概念,很快你將成為結構填充和打包的專家!未來的科技超級明星,快樂編程!

Credits: Image by storyset