// 定義 (標籤 + 類型別名)
struct Date {
int year;
int month;
int day;
};
typedef struct {
char *name;
int id;
struct Date birthday; // 巢狀結構體
} Student;
// 等價於
struct Student {
char *name;
int id;
struct Date birthday;
};
// 1. 逐欄位指派
Student s1;
s1.name = "Alice";
s1.id = 1001;
s1.birthday.year = 2000;
s1.birthday.month = 5;
s1.birthday.day = 20;
// 2. 指定初始化器 (C99) — 推薦,順序無關、可跳過欄位
Student s2 = {
.name = "Bob",
.id = 1002,
.birthday = { .year = 1999, .month = 12, .day = 31 }
};
// 3. 順序初始化 (舊式) — 必須完全依宣告順序
Student s3 = { "Charlie", 1003, { 1998, 8, 15 } };
// 4. 複合字面量 (C99) — 臨時物件
Student *ps = &(Student){ .name = "Temp", .id = 999, .birthday = {2020,1,1} };
Student stu = { "David", 1004, { 1997, 3, 10 } };
Student *p = &stu;
// 兩種存取方式等價
p->id = 1005; // 箭頭運算子 (慣用)
(*p).id = 1005; // 解參考 + 點運算子
// 指標運算:陣列中步進
Student arr[3] = { ... };
Student *ptr = arr;
for (int i=0; i<3; i++) {
printf("%s\n", ptr->name);
ptr++; // 移動 sizeof(Student) 位元組
}
// 靜態陣列
Student class_a[30];
// 動態陣列
Student *dyn = malloc(50 * sizeof(Student));
if (!dyn) return -1;
dyn[0].name = strdup("Eve"); // 字串需額外分配
// ...
for (int i=0; i<50; i++) free(dyn[i].name);
free(dyn);
| 方式 | 語法 | 拷貝開銷 | 可修改原物件 | 適用場景 |
|---|---|---|---|---|
| 傳值 | void Func(Student s) | 整個結構體拷貝 (堆疊) | 否 (修改副本) | 小型結構體 (< 16-32 bytes)、需語意上獨立副本 |
| 傳指標 | void Func(Student *s) | 指標大小 (8/4 bytes) | 是 | 大型結構體、需修改、C 風格慣用 |
| 傳常數指標 | void Func(const Student *s) | 指標大小 | 否 (編譯器保護) | 唯讀存取大型結構體 |
| C++ 參考 | void Func(Student& s) / const Student& | 指標大小 | 是/否 | C++ 專案首選 |
// 範例:以指標修改
void UpdateID(Student *s, int new_id) {
if (s) s->id = new_id;
}
// 範例:以常數指標唯讀
void PrintStudent(const Student *s) {
if (!s) return;
printf("Name: %s, ID: %d, Birthday: %04d-%02d-%02d\n",
s->name, s->id, s->birthday.year, s->birthday.month, s->birthday.day);
}
alignof(type))。
struct PaddingExample {
char a; // offset 0, size 1
// 3 bytes padding
int b; // offset 4, size 4 (4-byte aligned)
char c; // offset 8, size 1
// 3 bytes padding (tail)
}; // sizeof = 12 (max align 4)
struct PackedExample {
char a;
char c;
int b;
}; // sizeof = 8 (無內部填充,尾部 3 bytes padding)
#pragma pack / __attribute__((packed)) — 壓縮結構體
// 1 byte 對齊 (無填充)
#pragma pack(push, 1)
struct Packed {
char a;
int b;
char c;
};
#pragma pack(pop)
// sizeof = 6, 但存取 int b 可能觸發未對齊存取例外 (ARM/SPARC) 或效能下降 (x86)
// GCC/Clang 屬性寫法
struct __attribute__((packed)) Packed2 { char a; int b; char c; };
警告:僅用於網路封包、檔案格式、硬體暫存器映射等外部規定佈局場景,一般業務邏輯禁用。
offsetof 宏 — 標準化取得成員偏移
#include <stddef.h>
size_t off = offsetof(Student, birthday.month); // 標準 C,編譯期常數
// 實現原理 (非標準,僅供理解):
// #define offsetof(type, member) ((size_t)&(((type*)0)->member))
struct Flags {
unsigned int is_active : 1;
unsigned int is_admin : 1;
unsigned int level : 4; // 0-15
unsigned int reserved : 26; // 填滿 32 bits
};
// sizeof = 4 (若 unsigned int 32 bits)
// 存取如同普通成員: f.is_active = 1;
&f.is_active 錯誤)。
struct VariableBuffer {
size_t len;
char data[]; // 柔性陣列,必須為最後一個成員,前面至少一個非柔性成員
};
// 分配
struct VariableBuffer *vb = malloc(sizeof(*vb) + 1024);
vb->len = 1024;
memcpy(vb->data, "Hello", 5);
// 釋放單一 free(vb) 即可
優於 char *data:單次分配、緩存局部性佳、免雙重指標。
| 錯誤 | 後果 | 修正 |
|---|---|---|
結構體定義未加分號 struct S {int a;} | 編譯錯誤,後續宣告被視為變數 | 加 ; |
| 傳值巨大結構體導致堆疊溢位/效能差 | 每次呼叫拷貝數百位元組 | 改傳 const Student* |
字串指標淺拷貝導致雙重 free 或懸空指標 | s2 = s1; free(s1.name); s2.name 成野指標 | 深拷貝 strdup / 參考計數 / std::string |
直接 memcmp/memcpy 含填充位元組的結構體 | 填充位元組未定義值導致比較失敗/資料洩漏 | 逐欄位比較/複製,或 memset(&s, 0, sizeof(s)) 後再用 |
| 位元欄位用於跨平台序列化 | 大端/小端佈局不同,資料損壞 | 手工位運算打包/解包 |
.h,不透明指標 隱藏內部佈局 (僅前向宣告 struct Foo;),實作於 .c。Foo_Create()/Foo_Destroy()/Foo_Copy() 封裝生命週期。_Static_assert(sizeof(Foo) == EXPECTED, "layout changed") 守護 ABI 穩定。const Foo*,小型 (< 16 bytes) 可傳值 (編譯器常優化為寄存器)。protobuf-c、flatbuffers、MessagePack 等 IDL 工具,勿手寫。結構體是 C 語言資料建模的核心。精通初始化語法、指標操作、函數傳遞策略、記憶體對齊與填充、位元欄位、柔性陣列、不透明指標封裝,能讓你設計出記憶體高效、ABI 穩定、易於測試與維護的資料結構。下次定義 struct 時,先思考:誰擁有記憶體?如何傳遞?是否需序列化?對齊是否影響效能? — 答案導向正確設計。