多线程模拟du命令程序内存泄漏排查求助
我是编程新手,目前正在编写一个多线程模拟du命令的程序。程序运行正常,但存在内存泄漏问题,不知该如何修复。
以下是出现问题的函数:
void addSubdirectories(const char *basePath) { linkedlist *stack = linkedlist_empty(); if (stack == NULL) { return; // 内存分配失败则退出函数 } searching((char *)basePath, &dir_lock); linkedlist_insert(linkedlist_first(stack), strdup(basePath)); // 将基准路径插入链表 while (!linkedlist_isEmpty(stack)) { char *currentDir = strdup(linkedlist_inspect(linkedlist_first(stack))); if (currentDir == NULL) { break; // 内存分配失败则退出循环 } linkedlist_remove(linkedlist_first(stack)); DIR *dir = opendir(currentDir); if (dir == NULL) { free(currentDir); continue; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_DIR) { if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { char subDirPath[MAX_PATH_LENGTH]; snprintf(subDirPath, sizeof(subDirPath), "%s/%s", currentDir, entry->d_name); // 将子目录加入链表待遍历 searching(subDirPath, &dir_lock); linkedlist_insert(linkedlist_first(stack), strdup(subDirPath)); // 将子目录插入栈 } } } closedir(dir); free(currentDir); // 从链表移除后释放currentDir内存 } linkedlist_free(stack); }
我使用Valgrind定位到了泄漏点,但所有释放内存的尝试都失败了,检测结果如下:
10,982 bytes in 500 blocks are definitely lost in loss record 10 of 10 at 0x48407B4: malloc (vg_replace_malloc.c:381) by 0x49227F9: strdup (strdup.c:42) by 0x10A25B: addSubdirectories (.c:329) by 0x109734: main (.c:28)
补充使用的链表实现代码:
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef struct linkedlist_node { void *data; struct linkedlist_node *next; struct linkedlist_node *prev; } linkedlist_node; typedef struct { linkedlist_node *first; linkedlist_node *last; } linkedlist; typedef struct { linkedlist *list; linkedlist_node *node; } linkedlist_pos; linkedlist *linkedlist_empty(void) { linkedlist *list = malloc(sizeof(linkedlist)); if (list == NULL) { return NULL; } list->first = NULL; list->last = NULL; return list; } bool linkedlist_isEmpty(const linkedlist *list) { return list->first == NULL; } linkedlist_pos linkedlist_first(const linkedlist *list) { linkedlist_pos pos; pos.list = (linkedlist *)list; pos.node = list->first; return pos; } linkedlist_pos linkedlist_last(const linkedlist *list) { linkedlist_pos pos; pos.list = (linkedlist *)list; pos.node = list->last; return pos; } linkedlist_pos linkedlist_next(linkedlist_pos pos) { linkedlist_pos next_pos; next_pos.list = pos.list; next_pos.node = pos.node->next; return next_pos; } linkedlist_pos linkedlist_prev(linkedlist_pos pos) { linkedlist_pos prev_pos; prev_pos.list = pos.list; prev_pos.node = pos.node->prev; return prev_pos; } bool linkedlist_isEnd(linkedlist_pos pos) { return pos.node == NULL; } void *linkedlist_inspect(linkedlist_pos pos) { return pos.node->data; } bool linkedlist_insert(linkedlist_pos pos, void *data) { linkedlist_node *new_node = malloc(sizeof(linkedlist_node)); if (new_node == NULL) { return false; } new_node->data = data; new_node->next = pos.node; new_node->prev = NULL; if (pos.node != NULL) { new_node->prev = pos.node->prev; pos.node->prev = new_node; } else { new_node->prev = pos.list->last; if (pos.list->last != NULL) { pos.list->last->next = new_node; } else { pos.list->first = new_node; } pos.list->last = new_node; return true; } if (new_node->prev != NULL) { new_node->prev->next = new_node; } else { pos.list->first = new_node; } return true; } void linkedlist_remove(linkedlist_pos pos) { linkedlist_node *node = pos.node; if (node->prev != NULL) { node->prev->next = node->next; } else { pos.list->first = node->next; } if (node->next != NULL) { node->next->prev = node->prev; } else { pos.list->last = node->prev; } free(node); } void linkedlist_free(linkedlist *list) { while (!linkedlist_isEmpty(list)) { linkedlist_remove(linkedlist_first(list)); } free(list); }
问题原因
Valgrind检测到的泄漏来自你用strdup插入链表的字符串。你的链表实现有个关键问题:linkedlist_remove和linkedlist_free只释放了链表节点本身,完全没处理节点里存储的data(也就是strdup出来的字符串)。每次你调用linkedlist_insert(..., strdup(...))时,都会分配一块新内存,但这些内存从来没被释放,直接导致了泄漏。
另外,你在循环里的操作也加重了泄漏:你先通过strdup(linkedlist_inspect(...))复制了一份字符串到currentDir,然后调用linkedlist_remove删掉节点,但此时节点里的原字符串还没被释放,直接丢了。
修复方案
有两种简单的修复方式,选一种就行:
方案1:修改链表实现,让它自动释放节点数据
直接修改linkedlist_remove函数,在释放节点前先释放存储的字符串:
void linkedlist_remove(linkedlist_pos pos) { linkedlist_node *node = pos.node; if (node->prev != NULL) { node->prev->next = node->next; } else { pos.list->first = node->next; } if (node->next != NULL) { node->next->prev = node->prev; } else { pos.list->last = node->prev; } free(node->data); // 新增这行,释放strdup分配的字符串 free(node); }
这个方案适合你的场景,因为你用链表存储的都是strdup分配的动态内存,修改后linkedlist_free会自动遍历所有节点并释放数据和节点本身,不用改业务代码。
方案2:在业务代码中手动释放链表节点的数据
如果不想修改链表实现(比如链表还要用来存储其他不需要释放的数据),就在使用链表的代码里手动处理:
- 循环中移除节点前,先取出节点里的字符串并释放:
while (!linkedlist_isEmpty(stack)) { // 先取出原字符串指针 char *originalDir = linkedlist_inspect(linkedlist_first(stack)); char *currentDir = strdup(originalDir); if (currentDir == NULL) { // 内存分配失败时,也要先释放原字符串再清理节点 free(originalDir); linkedlist_remove(linkedlist_first(stack)); break; } // 释放原字符串后再移除节点 free(originalDir); linkedlist_remove(linkedlist_first(stack)); // 后面的代码保持不变... }
- 函数末尾,在调用
linkedlist_free前,要释放链表中剩余节点的所有数据(如果循环提前break的话):
// 释放剩余节点的字符串数据 while (!linkedlist_isEmpty(stack)) { char *path = linkedlist_inspect(linkedlist_first(stack)); free(path); linkedlist_remove(linkedlist_first(stack)); } linkedlist_free(stack);
额外优化建议
你在循环里做了一次strdup,其实完全没必要——直接用linkedlist_inspect拿到的原字符串指针就可以操作,不用复制一份,这样还能减少一次内存分配,避免额外的内存管理:
while (!linkedlist_isEmpty(stack)) { char *currentDir = linkedlist_inspect(linkedlist_first(stack)); if (currentDir == NULL) { linkedlist_remove(linkedlist_first(stack)); break; } linkedlist_remove(linkedlist_first(stack)); DIR *dir = opendir(currentDir); if (dir == NULL) { free(currentDir); // 这里直接释放原字符串 continue; } // ... 中间代码不变 ... closedir(dir); free(currentDir); // 释放原字符串 }
这样既减少了内存分配,也简化了内存管理逻辑。
内容的提问来源于stack exchange,提问作者Yohio

