在C語言中,當被調用函數需要修改主調函數中的指針變量(如分配動態內存並返回指針)時,必須傳遞指針的指針。對於二維字符串數組(char**),這就需要使用三級指針(char***)。本文詳細解析一個經典案例:主函數定義字符串數組,被調函數動態分配內存合併並排序,最後主函數釋放內存——並重點分析釋放函數導致程序卡死的常見陷阱。
char* p[] 和二維數組 char str[][6]sort() 函數合併兩組數據,動態分配內存,按字典序排序char*** p1 返回主函數free2() 釋放內存時程序卡死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;
}
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;
}
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 卡死,常見原因包括:
sort 中分配失敗提前返回,部分內存已分配但 *p1 未賦值,主函數仍調用 free2 導致釋放未初始化指針strcpy 時緩衝區大小計算錯誤(如 sizeof(strLen) 應為 strLen),導致堆元數據損壞,後續 free 觸發崩潰free 觸發斷點看似「卡死」,實為檢測到堆錯誤// 錯誤寫法(原代碼) 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語言實現「輸出型指針參數」的標準模式。本案例暴露的核心教訓:
sizeof(ptr) vs size 是高頻錯誤_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF) 定位破壞源頭修正 malloc 大小參數後,程序可正常運行並正確釋放內存。