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

C語言三級指針實現主調函數動態分配二維字符串數組排序

     編輯:關於C語言

C語言三級指針實現主調函數動態分配二維字符串數組排序

在C語言中,當被調用函數需要修改主調函數中的指針變量(如分配動態內存並返回指針)時,必須傳遞指針的指針。對於二維字符串數組(char**),這就需要使用三級指針(char***)。本文詳細解析一個經典案例:主函數定義字符串數組,被調函數動態分配內存合併並排序,最後主函數釋放內存——並重點分析釋放函數導致程序卡死的常見陷阱。

問題場景

  • 主函數擁有兩組字符串:指針數組 char* p[] 和二維數組 char str[][6]
  • 調用 sort() 函數合併兩組數據,動態分配內存,按字典序排序
  • 排序結果通過三級指針 char*** p1 返回主函數
  • 主函數調用 free2() 釋放內存時程序卡死

核心代碼實現

1. 主函數

int main(void) {
    char* p[] = { "11111", "22222", "33333" };
    char str[][6] = { "aaaaa", "bbbbb", "ccccc" };
    char** p1 = NULL;
    int len1 = sizeof(p)/sizeof(*p);  // 3
    int len2 = 3;
    int len3 = 0;

    int ret = sort(p, len1, str, len2, &p1, &len3);
    if (ret == 0) {
        for (int i = 0; i < len3; i++) {
            printf("%s\n", p1[i]);
        }
        free2(&p1, len3);  // 此處卡死
    }
    return 0;
}

2. 排序函數(三級指針參數)

int sort(char** p, int len1, char (*str)[6], int len2, char*** p1, int* len3) {
    // 1. 分配指針數組內存:(len1+len2) 個 char*
    char** tempP = (char**)malloc(sizeof(char*) * (len1 + len2));
    if (!tempP) return -1;

    // 2. 複製第一組字符串(指針數組)
    for (int i = 0; i < len1; i++) {
        int strLen = strlen(p[i]) + 1;
        tempP[i] = (char*)malloc(strLen);
        if (!tempP[i]) return -2;
        strcpy(tempP[i], p[i]);
    }

    // 3. 複製第二組字符串(二維數組)
    for (int j = 0; j < len2; j++, i++) {
        int strLen = strlen(str[j]) + 1;
        tempP[i] = (char*)malloc(strLen);
        if (!tempP[i]) return -3;
        strcpy(tempP[i], str[j]);
    }

    // 4. 冒泡排序(交換指針,不移動字符串內容)
    int total = len1 + len2;
    for (int x = 0; x < total; x++) {
        for (int y = x + 1; y < total; y++) {
            if (strcmp(tempP[x], tempP[y]) > 0) {
                char* tmp = tempP[x];
                tempP[x] = tempP[y];
                tempP[y] = tmp;
            }
        }
    }

    *len3 = total;
    *p1 = tempP;  // 通過三級指針返回結果
    return 0;
}

3. 釋放函數(導致卡死的關鍵)

void free2(char*** myp, int len) {
    if (!myp) return;
    char** p = *myp;  // 還原為二級指針
    if (!p) return;

    for (int i = 0; i < len; i++) {
        free(p[i]);   // 釋放每個字符串
    }
    free(p);          // 釋放指針數組
    *myp = NULL;      // 置空原指針
}

卡死根本原因深度分析

代碼邏輯看似正確,但運行時在 free2 卡死,常見原因包括:

  1. 重複釋放/野指針:若 sort 中分配失敗提前返回,部分內存已分配但 *p1 未賦值,主函數仍調用 free2 導致釋放未初始化指針
  2. 堆損壞:strcpy 時緩衝區大小計算錯誤(如 sizeof(strLen) 應為 strLen),導致堆元數據損壞,後續 free 觸發崩潰
  3. 調試器偽象:VS2013調試模式下堆檢查嚴格,free 觸發斷點看似「卡死」,實為檢測到堆錯誤

關鍵Bug修正:malloc大小計算錯誤

// 錯誤寫法(原代碼)
tempP[i] = (char*)malloc(sizeof(strLen));  // sizeof(int) = 4 字節!

// 正確寫法
tempP[i] = (char*)malloc(strLen);          // 實際字符串長度+1

這是導致堆損壞的核心原因:sizeof(strLen) 返回的是 int 類型大小(通常4字節),而非字符串長度。複製長字符串時必然越界寫入,破壞堆元數據,導致後續 free 崩潰。

完善的錯誤處理模式

int sort(...) {
    char** tempP = NULL;
    // ... 分配邏輯 ...
    // 任何失敗路徑都要釋放已分配內存
    if (alloc_failed) {
        for (int k = 0; k < i; k++) free(tempP[k]);
        free(tempP);
        return ERROR_CODE;
    }
    // 成功才賦值輸出參數
    *p1 = tempP;
    *len3 = total;
    return 0;
}

總結

三級指針是C語言實現「輸出型指針參數」的標準模式。本案例暴露的核心教訓:

  1. malloc大小參數必須是字節數,而非變量大小——sizeof(ptr) vs size 是高頻錯誤
  2. 動態內存分配函數必須建立完整的「分配-檢查-失敗回滾」機制
  3. 調試堆錯誤時,啟用 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF) 定位破壞源頭

修正 malloc 大小參數後,程序可正常運行並正確釋放內存。

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