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

C 語言指標進階:指標的指標與野指標問題完整解析

     編輯:關於C語言

二級指標與野指標:記憶體管理的關鍵陷阱與正確模式

指標的指標 (`T**`) 是修改呼叫端指標變數本身 (而非指向物件) 的唯一標準方式。野指標則是指向已釋放/未初始化/非法記憶體的指標,是 C/C++ 最隱蔽的錯誤來源之一。

傳值 vs 傳址:為何需要二級指標


// 場景:函數內部配置記憶體,需讓呼叫端指標指向新記憶體

// ❌ 錯誤:一級指標傳值,修改的是副本
void allocate_wrong(int* p, size_t n) {
    p = malloc(n * sizeof(int));  // 修改區域副本 p
    // 呼叫端指標未改變 → 記憶體洩漏 + 野指標
}

// ✅ 正確:二級指標傳址,修改呼叫端指標本身
void allocate_correct(int** pp, size_t n) {
    *pp = malloc(n * sizeof(int));  // 解參考一次,修改呼叫端指標
}

// 使用
int* ptr = NULL;
allocate_correct(&ptr, 100);  // 傳入指標位址
// ptr 現在指向新配置的記憶體
free(ptr);

程式碼範例深度剖析


int _tmain(int argc, _TCHAR* argv[]) {
    // 案例 1:一級指標傳值 (修改指向物件,非指標本身)
    int* p = malloc(sizeof(int));
    flee(p);           // 傳入 p 的副本
    printf("%d", *p);  // 輸出 20 (物件被修改)
    free(p);
    
    // 案例 2:二級指標傳址 (修改指標本身)
    int* i = malloc(sizeof(int));
    flee2(&i);         // 傳入 &i (int**)
    printf("%d", *i);  // 輸出 5
    free(i);
}

void flee(int* q) {
    *q = 20;  // 修改指向的 int 物件
    // q 是副本,函數結束銷毀,不影響外部 p
}

void flee2(int** q) {
    // 危險模式:指向區域變數
    // int local = 5;
    // *q = &local;  // ❌ 野指標!local 生命週期結束
    
    // 安全模式:修改指向的物件內容
    if (q && *q) {
        **q = 5;  // 修改呼叫端指標指向的 int
    }
}

野指標四大成因與防範

成因程式碼模式防範對策
未初始化`int* p; *p = 10;`宣告即初始化:`int* p = NULL;` 或 `int* p = &var;`
釋放後未置空`free(p); *p = 5;``free(p); p = NULL;` (雙重 free 安全,解參考 NULL 立即崩潰可被發現)
指向區域變數`int* f() { int x; return &x; }`回傳動態配置記憶體、靜態變數、或呼叫端提供緩衝區
記憶體重新配置/移動`realloc` 後舊指標失效統一使用 `realloc` 回傳值更新所有別名指標

野指標偵測工具與技巧

  • AddressSanitizer (ASan):`-fsanitize=address` (GCC/Clang),執行期檢測 use-after-free、buffer-overflow
  • Valgrind Memcheck:Linux 下記憶體錯誤檢測神器
  • Visual Studio /RTC:Run-Time Checks (`/RTC1` 啟用堆疊框架檢查)
  • 靜態分析:Cppcheck、Clang Static Analyzer、Coverity、PVS-Studio
  • 防禦性編程:`assert(p != NULL)`、封裝 `SAFE_FREE(p)` 巨集 (`do { free(p); p=NULL; } while(0)`)

二級指標實戰場景

  1. 函數輸出指標:`int read_file(const char* path, char** out_buf, size_t* out_len)`
  2. 鏈表節點刪除:`void delete_node(Node** head, Node* target)` 修改 `*head` 或前驅 `next`
  3. 二維動態陣列:`int** matrix = malloc(rows * sizeof(int*));` 每行獨立配置
  4. COM/Windows API:`HRESULT CreateInstance(IUnknown** ppv)` 輸出介面指標
  5. C++ 智慧指標互操作:`unique_ptr.get()` 取得 `T*`,`&unique_ptr` 為 `unique_ptr*` (罕用)

現代 C++ 替代方案

  • `std::unique_ptr` / `std::shared_ptr`:自動生命週期管理,消除野指標
  • `std::optional` / `std::reference_wrapper`:非擁有引用語意
  • `gsl::not_null` (Guideline Support Library):編譯期/執行期保證非空
  • 參考傳遞 `T*&` 替代 `T**`:`void allocate(int*& p, size_t n) { p = new int[n]; }`

二級指標是 C 語言模擬「引用傳遞」的標準手法,野指標則是手動記憶體管理的代價。理解指標層級、所有權轉移、生命週期綁定,配合現代工具與 C++ RAII 機制,能將這類錯誤降至最低。

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