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

C语言函数内存释放问题:Valgrind检测到堆内存泄漏

堆内存泄漏排查与内存管理实践问题

初始Valgrind泄漏报告

==696839== HEAP SUMMARY:
==696839==     in use at exit: 136 bytes in 1 blocks
==696839==   total heap usage: 6 allocs, 5 frees, 12,624 bytes allocated
==696839== 
==696839== 136 bytes in 1 blocks are definitely lost in loss record 1 of 1
==696839==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==696839==    by 0x109A35: insert_word_into_table (dictionary.c:57)
==696839==    by 0x109BD9: dict_insert (dictionary.c:82)
==696839==    by 0x109606: main (spell_check.c:91)
==696839== 
==696839== LEAK SUMMARY:
==696839==    definitely lost: 136 bytes in 1 blocks
==696839==    indirectly lost: 0 bytes in 0 blocks
==696839==      possibly lost: 0 bytes in 0 blocks
==696839==    still reachable: 0 bytes in 0 blocks
==696839==         suppressed: 0 bytes in 0 blocks

出错的insert_word_into_table函数代码

int insert_word_into_table(table_t *table, const char *word){
    int hsh_code = hash_code(word);
    int duplicateWordFlag = 0;

    //initialize the new node for the word
    list_node_t *newNode = malloc(sizeof(list_node_t));//line 57 that error is referring to
    //printf("%lu", sizeof(list_node_t)); checking the size, it is 136 bytes

    if (table->array[hsh_code] == NULL) {//if nothing is in the array
        table->array[hsh_code] = newNode;
        table->array[hsh_code]->next = NULL;
        strcpy(table->array[hsh_code]->word, word);
    } else {//if there is a node there
        list_node_t* i = table->array[hsh_code];
        while (i->next != NULL) {
            if (strcmp(word, i->word) == 0){
                duplicateWordFlag = -1;
            }
            i = i->next;
        }
        i->next = malloc(sizeof(list_node_t));
        strcpy(i->next->word, word);
        i->next->next = NULL;
    }
    return duplicateWordFlag;
}

尝试过的修复步骤

  • 疑问:函数内分配的堆内存会在函数退出时自动释放吗?原本以为在程序末尾释放整个链表数组内存即可解决问题。
  • 修改函数将i->next = malloc(sizeof(list_node_t))改为i->next = newNode,错误仍存在。
  • 最初的释放函数(程序末尾调用):
void dict_free(dictionary_t *dict) {
    free(dict->table->array);//base array of linked lists
    free(dict->table);
    free(dict);
}
  • 修改释放函数遍历数组释放每个节点,暂时解决了问题:
void dict_free(dictionary_t *dict) {
    for (int i = 0; i < dict->table->length; i++) {
        free(dict->table->array[i]);
    }
    free(dict->table->array);
    free(dict->table);
    free(dict);
}
  • 意识到未释放所有链表节点,再次修改为遍历所有链表节点释放,但Valgrind仍检测到泄漏:
//free the table; a structure that is an array of linked lists
void table_free(table_t *table) {
        list_node_t *node;
        list_node_t *tempNode;
        for (int i = 0; i < table->length; i++) {
            if (table->array[i] != NULL) {
                if (table->array[i]->next == NULL) {//if only one node in linked list
                    free(table->array[i]);
                    continue;
                } else {
                    node = table->array[i];
                    while(node->next != NULL) {
                        tempNode = node->next;
                        free(node);
                        node = tempNode;
                    }
                }
            }
        }

        for (int i = 0; i < table->length; i++) {
            if (table->array[i] == NULL) { //free the spots I missed
                free(table->array[i]);
            }
        }
        free(table->array);
        free(table);
}

void dict_free(dictionary_t *dict) {
    //old way not freeing everything
    // for (int i = 0; i < dict->table->length; i++) {
    //     free(dict->table->array[i]);
    // }
    // free(dict->table->array);
    // free(dict->table);
    // free(dict);    

    //new attempt
    table_free(dict->table);
    free(dict);
}

此时Valgrind报告:

==4020884== HEAP SUMMARY:
==4020884==     in use at exit: 97,376 bytes in 716 blocks
==4020884==   total heap usage: 8,606 allocs, 7,890 frees, 1,209,040 bytes allocated
==4020884== 
==4020884== 97,376 bytes in 716 blocks are definitely lost in loss record 1 of 1
==4020884==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4020884==    by 0x109B32: insert_word_into_table (dictionary.c:56)
==4020884==    by 0x109C8E: dict_insert (dictionary.c:97)
==4020884==    by 0x10A16A: read_dict_from_text_file (dictionary.c:245)
==4020884==    by 0x10981A: main (spell_check.c:142)

核心疑问

大型程序中仅在末尾释放所有分配的内存,是否是良好实践且能确保完全释放?我在多个函数中分配内存但未即时释放。


问题分析与解决方案

  1. 堆内存不会随函数退出自动释放:C语言中,malloc分配的堆内存必须显式调用free释放,函数退出时不会自动回收。你最初的泄漏根源在于insert_word_into_table的else分支:每次都提前malloc了newNode,但完全没用到它,反而重新malloc新节点,导致newNode的内存永久丢失。

  2. 释放函数的逻辑漏洞:你最后写的table_free有两个关键问题:

    • 链表遍历释放时,仅释放了链中的前n-1个节点,最后一个节点(node->next == NULL时)未被释放,指针直接丢失造成泄漏。
    • 第二个循环尝试free(NULL),虽然合法但完全多余,暴露了对指针状态的误解。

    正确的链表节点释放逻辑:

    void table_free(table_t *table) {
        list_node_t *current, *next;
        for (int i = 0; i < table->length; i++) {
            current = table->array[i];
            while (current != NULL) {
                next = current->next;
                free(current);
                current = next;
            }
        }
        free(table->array);
        free(table);
    }
    
  3. 内存释放的实践规范:

    • 仅在程序末尾释放内存,对于短生命周期程序,操作系统会在进程退出后回收所有内存,但这不是良好实践:一是会掩盖内存泄漏问题,二是长期运行的服务会因泄漏耗尽内存。
    • 正确的做法是配对分配与释放:谁分配谁负责释放,或明确内存所有权。比如哈希表模块应提供完整的创建、插入、销毁接口,销毁接口负责释放所有内部内存,这样即使在大型程序中也能避免泄漏,代码更易维护。

针对代码的具体修复

  • 先修复insert_word_into_table的内存浪费问题,避免重复分配或未使用的内存泄漏:

    int insert_word_into_table(table_t *table, const char *word){
        int hsh_code = hash_code(word);
        int duplicateWordFlag = 0;
    
        list_node_t *newNode = malloc(sizeof(list_node_t));
        if (!newNode) { // 必须检查malloc是否成功
            return -1; // 或根据需求处理错误
        }
        strcpy(newNode->word, word);
        newNode->next = NULL;
    
        if (table->array[hsh_code] == NULL) {
            table->array[hsh_code] = newNode;
        } else {
            list_node_t* i = table->array[hsh_code];
            // 先检查头节点是否重复
            if (strcmp(word, i->word) == 0) {
                duplicateWordFlag = -1;
                free(newNode); // 重复则释放未使用的节点
                return duplicateWordFlag;
            }
            while (i->next != NULL) {
                i = i->next;
                if (strcmp(word, i->word) == 0) {
                    duplicateWordFlag = -1;
                    free(newNode); // 重复则释放未使用的节点
                    return duplicateWordFlag;
                }
            }
            i->next = newNode; // 使用已分配的节点,避免重复malloc
        }
        return duplicateWordFlag;
    }
    

    原代码在else分支中完全未使用提前分配的newNode,反而重新malloc,导致每次进入该分支都会泄漏一个节点内存,这也是后续Valgrind报告大量泄漏的核心原因。

  • 替换为正确的table_free函数,确保所有链表节点、数组及结构体都被完整释放。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 17:19:53