C语言无内存泄漏实现链表:内存丢失与循环链表问题排查
嘿,我帮你揪出代码里的两个核心错误了,这直接导致了你遇到的内存丢失和循环链表问题:
1. 内存分配的大小完全错了
你写的malloc(sizeof(headPart))是个典型的新手坑——headPart是RailPart*类型的指针,它的大小在32位系统是4字节、64位是8字节,但你的RailPart结构体大小肯定比这个大得多。这就导致你分配的内存远不足以容纳整个结构体,后续给toPush的成员赋值时,会直接越界写到堆上的其他内存区域,把之前节点的nextPart指针给覆盖掉了——这就是为什么加第4个节点后,第3个节点的指针变成异常地址,本质是内存被越界写破坏了。
修复方法:把malloc的参数改成sizeof(RailPart),这样才会分配整个结构体需要的内存:
RailPart *toPush = (RailPart*)malloc(sizeof(RailPart));
2. 局部数组越界,破坏了栈内存
你定义的sPart[1]和ePart[1]只有1个元素的空间,但用sscanf("%[^,],%[^,],...", sPart, ePart,...)的时候,%[^,]会读取到逗号前的所有字符,然后自动在末尾加'\0'字符串结束符。比如输入是"a,b,100,200",sPart会被写入'a',然后'\0'会写到sPart[1]——这个位置已经超出数组范围了,属于栈内存越界。
栈内存越界会乱改栈上的其他变量(比如循环计数器、指针、函数返回地址),这就是为什么你会遇到“未使用内存地址在sscanf后变化”甚至循环链表的情况——栈上的指针被越界写给篡改了。
修复方法:
不用搞数组,直接用单个字符变量配合%c格式符读取就行,彻底避免越界:
char sPart, ePart; sscanf(partInfo, "%c,%c,%[^,],%[^,]", &sPart, &ePart, len, price);
如果非要用数组,至少把大小改成2(比如char sPart[2];),能容纳字符加结束符,但显然用单个字符更直接。
额外的优化建议
- 给
sPartIdx和ePartIdx初始化默认值(比如-1),避免结构体里残留堆内存的脏数据; - 每次调用
sscanf和strtol后做个校验,比如检查sscanf的返回值是否为4(成功读取4个字段),strtol的noneDigit是否指向合法的位置,避免非法输入导致的崩溃; - 记得程序结束时遍历链表,逐个释放节点内存,避免内存泄漏。
修正后的完整代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_FIELD_SIZE 20 #define BASE_10 10 typedef struct RailPart { char start; char end; int sPartIdx; int ePartIdx; int len; int price; struct RailPart *nextPart; } RailPart; RailPart *headPart = NULL; void createRailPart(char *partInfo, char *connectTypes){ char sPart, ePart; char len[MAX_FIELD_SIZE]; char price[MAX_FIELD_SIZE]; char *noneDigit; // 修正内存分配大小 RailPart *toPush = (RailPart*)malloc(sizeof(RailPart)); if (toPush == NULL){ perror("malloc failed"); return; } // 初始化索引为默认值 toPush->sPartIdx = -1; toPush->ePartIdx = -1; // 修正sscanf读取方式,校验返回值 if (sscanf(partInfo, "%c,%c,%[^,],%[^,]", &sPart, &ePart, len, price) != 4) { fprintf(stderr, "Invalid partInfo format!\n"); free(toPush); // 分配失败要及时释放内存 return; } toPush->start = sPart; toPush->end = ePart; for (int i = 0; i < (int) strlen(connectTypes); i++){ if (toPush->end == connectTypes[i]){ toPush->ePartIdx = i; } if (toPush->start == connectTypes[i]){ toPush->sPartIdx = i; } } // 校验长度转换是否合法 toPush->len = (int) strtol(len, &noneDigit, BASE_10); if (*noneDigit != '\0' && *noneDigit != ',') { fprintf(stderr, "Invalid length value!\n"); free(toPush); return; } // 校验价格转换是否合法 toPush->price = (int) strtol(price, &noneDigit, BASE_10); if (*noneDigit != '\0') { fprintf(stderr, "Invalid price value!\n"); free(toPush); return; } // 插入链表头部 toPush->nextPart = headPart; headPart = toPush; }
内容的提问来源于stack exchange,提问作者Errgod

