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

C 指針與結構體進階應用:記憶體佈局、鏈表實作、動態管理完整指南

     編輯:關於C語言

C 指針與結構體進階應用:記憶體佈局、鏈表實作、動態管理完整指南

結構體聚合異構資料,指針串聯動態節點,兩者結合構成 C 語言資料結構的基石。本文深入解析結構體記憶體對齊、指針操作慣用模式、單/雙向鏈表完整實作、動態記憶體管理陷阱與防禦模式。

一、結構體記憶體佈局與對齊

typedef struct {
    char *firstName;
    char *lastName;
    char *title;
    unsigned int age;
} Person;

關鍵規則

  • 欄位順序:按宣告順序依遞增地址排列
  • 對齊填充:編譯器插入填充位元組滿足對齊要求(如 int 4 位元組對齊)
  • 結構體大小 ≥ 所有欄位大小之和,尾部常有填充使陣列元素對齊
  • offsetof(Person, age) 取得欄位偏移(stddef.h)

二、結構體初始化三種模式

1. 變數直接初始化

Person p = {0};
p.firstName = strdup("Emily");
p.age = 23;

2. 指針動態分配初始化

Person *ptr = malloc(sizeof(Person));
ptr->firstName = strdup("Emily");
ptr->age = 23;
// 等價於 (*ptr).age = 23

3. 指定欄位初始化(C99)

Person p = { .age = 23, .firstName = "Emily" };

三、結構體作為函數參數:三種傳遞策略

策略語法優點缺點適用場景
值傳遞void f(Person p)簡單、無副作用拷貝開銷大、無法修改原值小型結構、唯讀
指針傳遞void f(Person *p)零拷貝、可修改、支援動態分配需檢查空指針大型結構、需修改、動態節點
const 指針void f(const Person *p)零拷貝、唯讀保護不可修改大型結構、唯讀

四、鏈表完整實作指南

節點定義

typedef struct Node {
    int data;
    struct Node *next;  // 單向
    // struct Node *prev; // 雙向額外加此欄位
} Node;

單向鏈表核心操作

// 建立節點
Node* create_node(int val) {
    Node *n = malloc(sizeof(Node));
    if (!n) return NULL;
    n->data = val;
    n->next = NULL;
    return n;
}

// 尾插法建表(帶頭節點)
Node* build_list(int *arr, int n) {
    Node head = {0, NULL};
    Node *tail = &head;
    for (int i = 0; i < n; ++i) {
        tail->next = create_node(arr[i]);
        if (!tail->next) { free_list(head.next); return NULL; }
        tail = tail->next;
    }
    return head.next;
}

// 遍歷
void traverse(Node *head) {
    for (Node *p = head; p; p = p->next)
        printf("%d ", p->data);
    puts("");
}

// 查找
Node* find(Node *head, int val) {
    for (Node *p = head; p; p = p->next)
        if (p->data == val) return p;
    return NULL;
}

// 插入(在第 pos 個節點後)
bool insert_after(Node *head, int pos, int val) {
    Node *p = head;
    for (int i = 0; i < pos && p; ++i) p = p->next;
    if (!p) return false;
    Node *n = create_node(val);
    if (!n) return false;
    n->next = p->next;
    p->next = n;
    return true;
}

// 刪除(刪除第 pos 個節點)
bool delete_at(Node **head, int pos) {
    if (!*head) return false;
    if (pos == 0) {
        Node *tmp = *head;
        *head = (*head)->next;
        free(tmp);
        return true;
    }
    Node *p = *head;
    for (int i = 0; i < pos-1 && p->next; ++i) p = p->next;
    if (!p->next) return false;
    Node *tmp = p->next;
    p->next = tmp->next;
    free(tmp);
    return true;
}

// 釋放整表
void free_list(Node *head) {
    while (head) {
        Node *tmp = head;
        head = head->next;
        free(tmp);
    }
}

雙向鏈表關鍵差異

  • 節點額外含 prev 指針
  • 插入/刪除需維護雙向連結(4 步指針操作)
  • 支援反向遍歷、O(1) 刪除已知節點

五、動態記憶體管理:malloc/free/calloc/realloc

函數原型特性
mallocvoid* malloc(size_t size)分配未初始化記憶體,失敗返回 NULL
callocvoid* calloc(size_t n, size_t size)分配並歸零,防止未初始化讀取
reallocvoid* realloc(void* ptr, size_t size)調整大小,可能移動記憶體位址
freevoid free(void* ptr)釋放,ptr 為 NULL 安全無操作

六、記憶體管理鐵律與防禦模式

  1. 分配必檢查:if (!p) return ERR;
  2. 釋放必置空:free(p); p = NULL;
  3. 結構體含指針欄位時,先釋放內層再釋放外層:
    free(ptr->firstName);
    free(ptr->lastName);
    free(ptr);
  4. 避免釋放後使用(迷途指針)
  5. 避免重複釋放
  6. 敏感資料釋放前清零:memset(ptr, 0, size); free(ptr);
  7. 配對原則:每個 malloc/calloc/realloc 對應唯一 free

七、常見錯誤案例剖析

// 錯誤:釋放順序顛倒導致內層指針成迷途指針
free(ptr);           // 外層先釋放
free(ptr->name);     // 非法:ptr 已失效,ptr->name 讀取未定義行為

// 錯誤:未分配足夠空間給字串欄位
ptr->name = malloc(strlen(src)); // 缺少 +1 給 \0
strcpy(ptr->name, src);          // 越界寫入

// 正確:
ptr->name = malloc(strlen(src) + 1);
strcpy(ptr->name, src);
結構體記憶體佈局圖 鏈表節點結構圖 單向鏈表示意圖 雙向鏈表示意圖

結構體與指針的協奏,構建了 C 語言動態資料結構的無限可能。掌握記憶體佈局、所有權轉移、生命週期管理三大核心,方能馭駕複雜系統而不失控。

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