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

C語言結構體參數傳遞陷阱:從空指針崩潰到正確實踐

     編輯:關於C語言

前言:結構體傳參——看似簡單,實則暗藏殺機

結構體作為函數參數傳遞,是 C 語言日常開發高頻操作。但「值傳遞還是指針傳遞?指針是否初始化?誰負責分配內存?」這幾個問題若不清晰,輕則邏輯錯誤,重則段錯誤崩潰。本文通過三個版本的迭代重構,演示從錯誤到正確的完整思考路徑。

一、 場景與結構體定義

typedef struct {
    INT8 szEmployeeName[100];
    UINT16 iEmployeeAge;
    UINT32 iEmployeeNo;
} TEmployeeInfo;

// 目標函數:填充員工信息
INT32 GetEmployeeInfo(TEmployeeInfo *ptEmployeeInfo);

二、 版本一:傳遞未初始化指針 → 崩潰

INT32 main(void) {
    TEmployeeInfo *ptEmployeeInfo = NULL;  // 只有指針變量,無實體內存!
    INT32 iRetValue = GetEmployeeInfo(ptEmployeeInfo); // 傳入 NULL
    // ...
}

INT32 GetEmployeeInfo(TEmployeeInfo *ptEmployeeInfo) {
    if (ptEmployeeInfo == NULL) return -1; // 防護生效,返回錯誤
    // 填充邏輯...
}

結果:函數內部檢測到 NULL,返回 -1,主函數打印錯誤。

版本一運行結果

圖1 版本一:指針為空,無法賦值

教訓:聲明指針 ≠ 分配內存。TEmployeeInfo *p 只是在棧上分配了 4/8 字節存地址,指向何處未知 (此處顯式置 NULL)。

三、 版本二:主函數先給指針賦值 → 內存錯誤

嘗試在調用前對指針指向的內容賦值:

INT32 main(void) {
    TEmployeeInfo *ptEmployeeInfo = NULL;
    // 直接解引用 NULL 指針寫入!未分配內存!
    strncpy((char*)ptEmployeeInfo->szEmployeeName, "Di Renjie", ...);
    ptEmployeeInfo->iEmployeeAge = 150;
    // ...
    GetEmployeeInfo(ptEmployeeInfo); // 傳入同一指針
}

結果:程式運行時觸發段錯誤或內存訪問違規。

版本二運行結果

圖2 版本二:出現內存問題

教訓:ptEmployeeInfo 仍是野指針/NULL,解引用寫入屬未定義行為。同一指針不能同時指向兩塊不同內存 (這裡邏輯混淆:主函數想預填,子函數想覆寫,實則都無合法內存)。

四、 版本三:正確姿勢——主函數分配實體,傳址給函數

INT32 main(void) {
    TEmployeeInfo tEmployeeInfo = {0};  // 棧上分配實體結構體,自動初始化為 0
    INT32 iRetValue = GetEmployeeInfo(&tEmployeeInfo); // 取地址傳遞
    if (iRetValue != 0) return -1;
    printf("員工信息: 姓名=%s, 年齡=%d, 工號=%d\n",
           tEmployeeInfo.szEmployeeName,
           tEmployeeInfo.iEmployeeAge,
           tEmployeeInfo.iEmployeeNo);
    return 0;
}

結果:正確輸出函數填充的數據。

版本三運行結果

圖3 版本三:執行結果正確

五、 核心原則總結

  1. 實體在調用方:被調用函數只負責「填充」,內存由調用方分配 (棧上 T t; func(&t) 或堆上 malloc),所有權清晰。
  2. 指針防護在被調用方:函數入口必判 if (ptr == NULL) return ERR,防禦性編程鐵律。
  3. 值傳遞慎用:大結構體值傳遞開銷大,且函數內修改不影響外部;需修改內容必傳指針。
  4. 初始化習慣:T t = {0}; 或 memset(&t, 0, sizeof(t)),避免未初始化臨時值干擾邏輯。
  5. 註釋規範:文件頭、函數頭、關鍵邏輯必註釋,體現工程素養。

六、 進階延伸:動態分配與所有權轉移

若結構體生命週期超出函數棧框,需堆分配:

TEmployeeInfo *p = malloc(sizeof(TEmployeeInfo));
if (!p) return -1;
GetEmployeeInfo(p);
// ... 使用 p ...
free(p); // 調用方負責釋放,所有權不轉移

若函數內部分配並返回 (工廠模式),需文檔明確「調用方擁有所有權,需 free」。

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