首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言結構體完全指南:定義、嵌套、指標、陣列、函數參數與記憶體對齊

     編輯:關於C語言

結構體基礎語法


// 定義 (標籤 + 類型別名)
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 錯誤)。
  • 跨平台位元序 (大端/小端) 佈局不同,禁止用於網路/檔案序列化。

柔性陣列成員 — 動態大小結構體 (C99)


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)) 後再用
位元欄位用於跨平台序列化大端/小端佈局不同,資料損壞手工位運算打包/解包

現代 C 專案建議

  • 所有結構體定義於 .h,不透明指標 隱藏內部佈局 (僅前向宣告 struct Foo;),實作於 .c。
  • 提供 Foo_Create()/Foo_Destroy()/Foo_Copy() 封裝生命週期。
  • 使用 _Static_assert(sizeof(Foo) == EXPECTED, "layout changed") 守護 ABI 穩定。
  • 大型結構體 (< 64 bytes) 統一傳 const Foo*,小型 (< 16 bytes) 可傳值 (編譯器常優化為寄存器)。
  • 網路/儲存序列化採用 protobuf-c、flatbuffers、MessagePack 等 IDL 工具,勿手寫。

結語

結構體是 C 語言資料建模的核心。精通初始化語法、指標操作、函數傳遞策略、記憶體對齊與填充、位元欄位、柔性陣列、不透明指標封裝,能讓你設計出記憶體高效、ABI 穩定、易於測試與維護的資料結構。下次定義 struct 時,先思考:誰擁有記憶體?如何傳遞?是否需序列化?對齊是否影響效能? — 答案導向正確設計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved