C语言链表valgrind提示list_add中malloc内存泄漏排查
Valgrind 报告链表内存泄漏问题排查
问题现象
Valgrind 检测到list_add函数中通过malloc分配的16字节内存发生明确泄漏,泄漏调用栈指向list_add中为新节点分配内存的代码行,但表面看节点已经正确挂入链表,且实现了递归释放函数free_list,逻辑上似乎不存在泄漏条件。
链表设计规则:头节点(newlist返回的初始节点)的object始终为NULL,实际存储业务数据的节点从头节点的后继节点开始存放。
相关代码
链表结构体定义(list.h)
typedef struct _linekd_list{ void* object; struct _linked_list* next; }linked_list;
注意:此处结构体标签存在拼写错误,_linekd_list应为_linked_list,否则会触发编译类型不匹配问题
链表初始化函数(list.c)
linked_list* newlist(){ linked_list * list = malloc(sizeof(linked_list)); list->next = NULL; // 作为链表结束标记 list->object = NULL; return list; }
链表添加元素函数(list.c)
int list_add(void* new_object, linked_list* list){ while(list->next != NULL) { // 遍历到队列尾部 list = list->next; } list->next = malloc(sizeof(linked_list)); // Valgrind 提示此处分配的内存泄漏 list->next->next = NULL; // 新节点设置结束标记 list->next->object = new_object; // 存储实际对象指针 if(list->next->object == new_object) { return 0; } else { return 1; } return 0; }
Valgrind 泄漏报告
==33369== 16 bytes in 1 blocks are definitely lost in loss record 1 of 3 ==33369== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==33369== by 0x402219: list_add (list.c:11) ==33369== by 0x4012D0: main (test_list.c:38)
链表释放函数(list.c)
void free_list(linked_list* list){ if(list->next != NULL) { free_list(list->next); free(list); } }
泄漏根因
泄漏和list_add的内存分配逻辑无关,完全是free_list函数的逻辑错误导致:
- 现有释放逻辑仅当节点的
next指针不为空时,才会递归释放后继节点、再释放当前节点。 - 这意味着所有next为NULL的尾节点(包括仅存头节点的场景下的头节点本身)永远不会被释放:
- 当链表只有
newlist创建的头节点时,头节点next为NULL,调用free_list直接跳过所有逻辑,头节点直接泄漏。 - 当链表添加过元素后,遍历到最后一个实际存储数据的节点时,该节点
next为NULL,递归到这一层直接返回,不会执行free操作,最后一个节点永久泄漏,刚好对应Valgrind报告的16字节(单节点大小)泄漏。
- 当链表只有
- Valgrind把泄漏点记在
list_add的malloc行,是因为这块内存是在这里分配的,但本质是释放逻辑没有覆盖到这块内存。
修复方案
- 修正
free_list的释放逻辑,确保所有节点都能被释放,参考实现:
void free_list(linked_list* list){ if (list == NULL) { return; } // 先递归释放后继节点 free_list(list->next); // 无论next是否为空,当前节点都需要释放 free(list); }
- 修复结构体定义的拼写错误,避免类型不匹配问题:
typedef struct _linked_list{ void* object; struct _linked_list* next; }linked_list;
- 可选优化:补充
list_add的入参校验和malloc异常判断,避免空指针访问,同时删除函数末尾永远无法执行的冗余return 0:
int list_add(void* new_object, linked_list* list){ if (list == NULL) { return 1; } while(list->next != NULL) { list = list->next; } list->next = malloc(sizeof(linked_list)); if (list->next == NULL) { return 1; // 内存分配失败 } list->next->next = NULL; list->next->object = new_object; return 0; }
内容的提问来源于stack exchange,提问作者Toast
相关产品推荐
相关产品推荐

