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

C語言傳參陷阱:為什麼函數裡 malloc 不能改變外部指標?

     編輯:關於C語言

經典錯誤代碼


#include 
#include 
#include 

void getmemory(char *p) {  // 參數是指標**副本**
    p = (char *)malloc(100); // 修改的是副本,外部 str 不受影響
}

int main() {
    char *str = NULL;
    getmemory(str);        // 傳遞 str 的值 (NULL)
    strcpy(str, "hello world"); // str 仍為 NULL -> 段錯誤
    printf("%s\n", str);
    free(str);
    return 0;
}

結果:程式崩潰 (Segmentation fault)。getmemory 內的 malloc 分配的記憶體位址賦值給參數 p (區域變數),函數返回後 p 銷毀,外部 str 依然是 NULL,且分配的記憶體洩漏。

根本原因:C 語言只有「值傳遞」

void getmemory(char *p) 中,p 是指標變數,**按值傳遞**。呼叫時將 str 的值 (位址或 NULL) 複製給 p。函數內修改 p 指向新位址,不影響 str。

類比:str 是一張寫著地址的紙條。呼叫函數時,你把地址抄寫在另一張紙條 p 上給函數。函數在 p 上塗改新地址,你的原紙條 str 當然不變。

三種正確解法

方法 1:傳遞指標的指標 (二級指標) —— C 語言經典做法


void getmemory(char **p) {  // 接收指標的位址
    *p = (char *)malloc(100); // 解參考修改原指標指向
}

int main() {
    char *str = NULL;
    getmemory(&str);          // 傳遞 str 的位址
    strcpy(str, "hello world");
    printf("%s\n", str);      // 正常輸出
    free(str);
    return 0;
}

原理:&str 是 char**,函數透過 *p 間接修改 str 的值。

方法 2:函數回傳指標 —— 更直觀、現代風格


char *getmemory(void) {  // 回傳分配的位址
    char *p = (char *)malloc(100);
    return p;            // 將位址回傳給呼叫端
}

int main() {
    char *str = getmemory(); // 直接接收回傳值
    strcpy(str, "hello world");
    printf("%s\n", str);
    free(str);
    return 0;
}

優點:語意清晰「取得記憶體」、呼叫端明確擁有權、易於錯誤處理 (回傳 NULL 表示失敗)。

方法 3:靜態/全域緩衝區 (特定場景適用)


char *getmemory(void) {
    static char buf[100]; // 靜態儲存期,生命週期至程式結束
    return buf;
}

注意:非執行緒安全、不可重入、大小固定。僅適合簡單工具程式或單執行緒環境。

進階:封裝記憶體管理 (RAII 風格)


typedef struct {
    char *data;
    size_t size;
} Buffer;

bool buffer_init(Buffer *b, size_t size) {
    b->data = malloc(size);
    b->size = b->data ? size : 0;
    return b->data != NULL;
}

void buffer_free(Buffer *b) {
    free(b->data);
    b->data = NULL;
    b->size = 0;
}

// 使用
Buffer buf;
if (buffer_init(&buf, 100)) {
    strcpy(buf.data, "hello");
    printf("%s\n", buf.data);
    buffer_free(&buf);
}

將指標與大小綁定,提供初始化/釋放配對函數,避免洩漏、雙重釋放、野指標。

常見相關陷阱

錯誤代碼問題修正
void f(int *p) { p = &x; }修改區域指標副本傳 int** 或回傳 int*
void f(char *s) { s = "lit"; }指向字面常數,外部指標未變用 strcpy 複製內容,或回傳 const char*
void f(int a[]) { a = malloc(...); }陣列參數退化為指標,同第一類同方法 1 或 2
char *p; f(&p); // f 參數 void f(char *p)類型不匹配 (char** vs char*)函數參數改 char**

最佳實踐建議

  1. 優先回傳指標:T* create_T(),失敗回傳 NULL,呼叫端 if (!(p = create_T())) handle_error();
  2. 輸出參數用二級指標:bool get_T(T **out),需修改呼叫端指標時
  3. const 正確性:void read_only(const T *p) 承諾不修改 *p;void modify(T *const p) 承諾不修改 p (指標本身)
  4. 所有權語意文檔化:註解標註「呼叫端擁有回傳指標,須 free」或「緩衝區由呼叫端提供」
  5. 靜態分析工具:啟用編譯器警告 (-Wall -Wextra)、使用 cppcheck、clang-tidy 捕捉指標誤用

總結

C 語言指標傳遞本質是「值傳遞」。要在函數內修改外部指標指向,必須傳遞指標的位址 (二級指標) 或回傳新指標。理解這一點,能避開 90% 的指標相關 Bug。現代 C 風格傾向回傳指標而非輸出參數,語意更清晰、組合性更好。

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