You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言realloc调整字符串数组后如何正确释放动态内存

C语言动态路径列表的内存管理问题

问题描述

  • 实现目标:编写C语言函数维护可动态更新的路径列表,初始化默认路径为["/bin/"],通过change_paths函数可将路径列表替换为任意长度的新路径集合(包括空集合),例如将初始列表更新为["/temp/", "/auxillary/"]
  • 故障现象:程序末尾释放内存时,AddressSanitizer提示初始化阶段通过paths[0] = malloc(6 * sizeof(char));分配的6字节内存未被正确释放
  • 核心疑问:任意次数调用更新函数、传入任意数量新路径的场景下如何正确释放内存?当前使用的二级指针存储结构是否适配动态更新需求?

注:演示代码已做简化,例如char** new_paths = {"/temp/", "/auxillary/"};严格来说存在语法问题,仅作场景演示,不纠结该写法的标准性。

原始问题代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void change_paths(int *num_paths, char*** paths, int num_new_paths, char** new_paths){
   
    *paths = realloc(*paths, num_new_paths * sizeof(char*));

    for (int i = 0; i < num_new_paths; i++){
        *paths[i] = malloc((strlen(new_paths[i]) + 1) * sizeof(char));
        strcpy(*paths[i], new_paths[i]);
        }
}

int main(int argc, char *argv[]){
    // 初始化默认路径
    char** paths = malloc(1 * sizeof(char*));
    paths[0] = malloc(6 * sizeof(char));                         // <- realloc后这块内存如何释放?
    strcpy(paths[0], "/bin/");
    int num_paths = 1;

    // 更新为新的路径集合
    int num_new_paths = 2;
    char** new_paths = {"/temp/", "/auxillary/"};
    change_paths(&num_paths, &paths, num_new_paths, new_paths);

    // 释放内存
    for(int i = 0; i < num_new_paths; i++){
        free(paths[i]);
    }
    free(paths);

    return 0;
}

问题根因

当前用char**存储路径字符串指针数组、单独用整型变量记录路径数量的结构,完全适配动态更新需求,内存泄漏和报错来自三个代码逻辑错误:

  1. 旧内存未释放直接覆盖指针:调用realloc调整指针数组大小前,没有释放数组中原有指针指向的路径字符串内存。初始状态下paths[0]指向存储"/bin/"的6字节堆内存,直接调整指针数组大小后,这块内存的引用丢失,成为无法回收的泄漏内存,也就是Sanitizer报的问题。
  2. 运算符优先级错误:*paths[i]的写法等价于*(paths[i]),但传入的paths是三级指针char***,正确的访问方式是先解引用拿到二级指针,再访问下标,即(*paths)[i],原写法属于非法内存访问,触发未定义行为。
  3. 状态不同步:更新路径后没有修改*num_paths的计数值,同时没有处理num_new_paths = 0的边界场景,后续释放时循环边界错误,要么漏释放要么越界访问。

修正实现

修正逻辑遵循“覆盖指针前先释放旧内存”的原则:

  1. 更新路径前,先遍历释放当前列表中所有路径字符串的堆内存
  2. 单独处理新路径数量为0的场景,避免realloc分配0长度内存的实现兼容问题
  3. 修正指针访问的优先级错误,为新路径分配内存后拷贝内容
  4. 每次更新完成后同步修改路径计数,保证后续释放逻辑的循环边界正确

修正后的完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void change_paths(int *num_paths, char*** paths, int num_new_paths, char** new_paths){
    // 第一步:释放所有旧的路径字符串内存
    for (int i = 0; i < *num_paths; i++) {
        free((*paths)[i]);
    }

    // 处理新路径数为0的边界场景
    if (num_new_paths == 0) {
        free(*paths);
        *paths = NULL;
        *num_paths = 0;
        return;
    }

    // 调整指针数组大小,用临时变量接收返回值避免realloc失败丢失原指针
    char** temp = realloc(*paths, num_new_paths * sizeof(char*));
    if (temp == NULL) {
        perror("realloc failed");
        exit(1);
    }
    *paths = temp;

    // 分配新路径的内存并拷贝内容
    for (int i = 0; i < num_new_paths; i++){
        (*paths)[i] = malloc((strlen(new_paths[i]) + 1) * sizeof(char));
        strcpy((*paths)[i], new_paths[i]);
    }

    // 更新路径计数
    *num_paths = num_new_paths;
}

// 通用释放逻辑,任意状态下调用都不会泄漏
void free_paths(char** paths, int num_paths) {
    for (int i = 0; i < num_paths; i++) {
        free(paths[i]);
    }
    free(paths);
}

int main(int argc, char *argv[]){
    // 初始化默认路径
    char** paths = malloc(1 * sizeof(char*));
    paths[0] = malloc(6 * sizeof(char));
    strcpy(paths[0], "/bin/");
    int num_paths = 1;

    // 测试1:更新为2个路径
    char* new_paths1[] = {"/temp/", "/auxillary/"};
    change_paths(&num_paths, &paths, 2, new_paths1);

    // 测试2:更新为0个路径
    change_paths(&num_paths, &paths, 0, NULL);

    // 测试3:更新为3个路径
    char* new_paths2[] = {"/usr/bin/", "/home/user/", "/opt/"};
    change_paths(&num_paths, &paths, 3, new_paths2);

    // 统一释放
    free_paths(paths, num_paths);

    return 0;
}

内容的提问来源于stack exchange,提问作者kpz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:36:43