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

C語言指針原理(四十八):懸空指針形成與危害實戰演示

     編輯:關於C

本文通過鏈表操作實例,直觀演示懸空指針的形成過程:指針非空但指向已釋放/無效內存地址。這是 C/C++ 開發中最隱蔽、破壞力最強的內存錯誤之一。

懸空指針定義

一個指針不為 NULL,但指向一個已釋放、已銷毀或超出作用域的對象地址,稱為懸空指針。解引用該指針屬於未定義行為。

演示代碼:鏈表部分刪除導致懸空指針

#include 
#include 

typedef struct listnode mynode;
struct listnode {
    mynode *next;
    int number;
    int age;
};

mynode *addnode(mynode *prevnd, int number, int age) {
    mynode *ndtemp = (mynode*)malloc(sizeof(mynode));
    prevnd->next = ndtemp;
    ndtemp->number = number;
    ndtemp->age = age;
    ndtemp->next = NULL;
    return ndtemp;
}

mynode *initlist() {
    mynode *temp = (mynode*)malloc(sizeof(mynode));
    temp->number = 0; temp->age = 0; temp->next = NULL;
    return temp;
}

int main() {
    // 第一階段:正常構建、輸出、完整銷毀
    mynode *mylist = initlist();
    mynode *mytempnd = mylist;
    for (int i = 0; i < 10; i++) mytempnd = addnode(mytempnd, i, 20+i);
    for (mytempnd = mylist->next; mytempnd; mytempnd = mytempnd->next)
        printf("id:%d, age:%d\n", mytempnd->number, mytempnd->age);
    mynode *oldtmpnd;
    for (mytempnd = mylist->next; mytempnd; ) {
        printf("delete id:%d\n", mytempnd->number);
        oldtmpnd = mytempnd;
        mytempnd = mytempnd->next;
        free(oldtmpnd);
    }
    free(mylist);

    // 第二階段:重新構建,故意製造懸空指針
    mylist = initlist();
    mytempnd = mylist;
    for (int i = 0; i < 10; i++) mytempnd = addnode(mytempnd, i, 20+i);
    for (mytempnd = mylist->next; mytempnd; mytempnd = mytempnd->next)
        printf("id:%d, age:%d\n", mytempnd->number, mytempnd->age);

    printf("-------------------------\n");
    int j = 0;
    for (mytempnd = mylist->next; mytempnd; ) {
        oldtmpnd = mytempnd;
        mytempnd = mytempnd->next;
        if (++j > 6) { // 刪除第 7~10 節點
            printf("delete id:%d\n", oldtmpnd->number);
            free(oldtmpnd);
        }
    }
    // 此時第 6 個節點的 next 仍指向已被 free 的第 7 個節點地址 → 懸空指針
    return 0;
}

運行輸出

id:0, age:20
...
id:9, age:29
delete id:0 ... delete id:9
id:0, age:20 ... id:9, age:29
-------------------------
delete id:6
delete id:7
delete id:8
delete id:9

關鍵點解析

  1. 懸空指針產生點:第 6 節點的 next 原指向第 7 節點,第 7 節點被 free 後,第 6 節點的 next 未置 NULL,成為懸空指針。
  2. free 不置空:free(p) 僅釋放 p 指向的內存,不會修改 p 本身的值,也不会自動更新其他指向同一地址的指針。
  3. 內存狀態:釋放後內存被標記為可用,數據可能暫時殘留,但隨時可能被分配器覆蓋或返還給 OS,讀取屬於未定義行為。

防範最佳實踐

  • 釋放後立即置 NULL:free(ptr); ptr = NULL; 形成肌肉記憶。
  • 鏈表刪除同步更新前驅指針:刪除節點時,必須將前驅節點的 next 指向被刪節點的 next(或 NULL)。
  • 所有權清晰化:明確哪個模塊/指針擁有釋放責任,避免多重釋放或遺漏釋放。
  • 工具輔助:使用 AddressSanitizer (ASan)、Valgrind 等工具自動檢測懸空指針訪問。

總結

懸空指針比野指針(未初始化指針)更隱蔽,因為它持有「看起來合法」的非零地址。嚴格遵循「釋放即置空」、鏈表操作同步更新前驅指針、引入所有權語義,是 C 語言工程師避免此類災難性錯誤的基本功。

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