出现Invalid read of size 4错误,求修复及fscanf避坑方法
Invalid read of size 4错误(fscanf调用处)的修复与避坑指南
问题代码
Queue* recordQueue = newQueue(NULL, NULL); FILE* file = fopen(fileName, "r"); char* processName = malloc(8 * sizeof(char)); assert(processName != NULL); unsigned int arriveTime; unsigned int serviceTime; short memoryRequirement; while (fscanf(file, "%d %s %d %hd", &arriveTime, processName, &serviceTime, &memoryRequirement) != EOF) { ProcessStatus status = NOT_READY; ProcessInfo* processInfo = newProcessInfo(processName, arriveTime, serviceTime, memoryRequirement, status); processName = malloc(8 * sizeof(char)); assert(processName != NULL); inQueue(recordQueue, newNode(processInfo, NULL, NULL)); } return recordQueue;
Valgrind报错信息
==237688== Invalid read of size 4 ==237688== at 0x48C3AF2: __vfscanf_internal (vfscanf-internal.c:345) ==237688== by 0x48C329C: __isoc99_fscanf (isoc99_fscanf.c:30) ==237688== by 0x10A3AE: readProcessesFronFile (in /home/haozhec/project1/comp30023-2023-project-1/allocate) ==237688== by 0x10A91D: main (in /home/haozhec/project1/comp30023-2023-project-1/allocate) ==237688== Address 0xc0 is not stack'd, malloc'd or (recently) free'd ==237688== ==237688== ==237688== Process terminating with default action of signal 11 (SIGSEGV) ==237688== Access not within mapped region at address 0xC0 ==237688== at 0x48C3AF2: __vfscanf_internal (vfscanf-internal.c:345) ==237688== by 0x48C329C: __isoc99_fscanf (isoc99_fscanf.c:30) ==237688== by 0x10A3AE: readProcessesFronFile (in /home/haozhec/project1/comp30023-2023-project-1/allocate) ==237688== by 0x10A91D: main (in /home/haozhec/project1/comp30023-2023-project-1/allocate)
错误原因分析
- 文件指针未做有效性检查:如果
fopen打开文件失败,file会返回NULL,将NULL传入fscanf会直接触发非法内存访问,这是本次SIGSEGV的核心诱因。 - 格式符与变量类型不匹配:
arriveTime和serviceTime是unsigned int类型,但使用了对应signed int的%d格式符,会引发未定义行为,可能破坏内存布局。 - fscanf返回值判断逻辑错误:仅判断
!=EOF无法处理部分字段读取失败的情况,会导致循环陷入死循环,进一步引发内存问题。 - 冗余动态内存分配与泄漏:每次循环都重新malloc
processName,最后一次循环结束后,剩余的processName指针未释放,造成内存泄漏;同时频繁malloc增加了野指针风险。 - 未关闭文件:打开的文件未调用
fclose,导致文件描述符资源泄漏。
修复后的代码
Queue* recordQueue = newQueue(NULL, NULL); // 打开文件后立即检查有效性 FILE* file = fopen(fileName, "r"); if (!file) { fprintf(stderr, "无法打开文件: %s\n", fileName); // 必要时清理已分配资源后返回 return NULL; } // 使用栈缓冲区替代动态分配,更安全高效 char processName[8]; unsigned int arriveTime; unsigned int serviceTime; short memoryRequirement; // 判断是否成功读取全部4个字段,避免部分读取失败的死循环 while (fscanf(file, "%u %7s %u %hd", &arriveTime, processName, &serviceTime, &memoryRequirement) == 4) { ProcessStatus status = NOT_READY; ProcessInfo* processInfo = newProcessInfo(processName, arriveTime, serviceTime, memoryRequirement, status); inQueue(recordQueue, newNode(processInfo, NULL, NULL)); } // 务必关闭文件,释放资源 fclose(file); return recordQueue;
修复要点说明
- 强制检查文件指针:文件打开失败是常见场景,必须处理该情况,避免空指针传入IO函数。
- 格式符与变量严格对应:
unsigned int对应%u,字符串读取用%7s限制长度(缓冲区8字节,留1字节存终止符),防止溢出。 - 正确判断fscanf返回值:以成功读取的参数个数(此处为4)作为循环条件,确保每次读取都是完整有效的。
- 用栈变量替代不必要的动态分配:减少内存泄漏和野指针风险,同时提升性能。
- 及时释放资源:打开的文件必须调用
fclose关闭,避免资源泄漏。
使用fscanf的避坑指南
- 永远检查文件打开结果:只要涉及文件操作,第一步就是确认文件指针不为NULL。
- 严格匹配格式符与类型:参考C标准文档,确保每个格式符对应正确的变量类型,尤其是无符号类型、短整型、长整型等特殊类型。
- 限制字符串读取长度:使用
%Ns格式符(N为缓冲区容量-1),彻底避免缓冲区溢出导致的内存破坏。 - 不要依赖EOF判断循环:fscanf返回成功读取的字段数,用这个值判断是否完成一次有效读取,能避免部分读取失败的死循环。
- 减少不必要的动态内存:栈内存比堆内存更安全,无需手动管理,适合固定大小的临时缓冲区。
- 养成资源清理习惯:文件、动态内存等资源,在不再使用时必须及时释放,避免泄漏。
内容的提问来源于stack exchange,提问作者Howard Cui
相关产品推荐
相关产品推荐

