为何fread会将输出指针设为NULL并触发栈溢出错误?
二进制文件读写bool数组栈溢出问题修复
问题现象
尝试向二进制文件写入表示数组长度的int值,随后写入bool数组,写入操作可正常生成文件,但读取时触发栈溢出崩溃。
示例代码
#include <assert.h> #include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef struct cutlogTag{ int len; bool *parts_cut; } Cutlog; size_t save_cutlog(const char *path, const Cutlog *p){ FILE *fp; size_t written = 0; fp = fopen(path, "wb"); if (fp == NULL){ fprintf(stderr, "Failed to save cutlog file\n"); return 0; } written = fwrite(&(p->len), sizeof(p->len), 1, fp); written += fwrite(p->parts_cut, sizeof(bool), p->len, fp); if(written != 1 + p->len) fprintf(stderr, "error writing file\n"); else fprintf(stdout, "cutlog written to %s\n", path); fclose(fp); return written; } //returns cutlog with length of -1 on failure to load log Cutlog load_cutlog(const char *path){ Cutlog ret; FILE *fp; size_t read = 0; ret.len = -1; ret.parts_cut = NULL; fp = fopen(path, "rb"); assert(fp != NULL); fseek(fp, 0, SEEK_SET); fread(&ret.len, sizeof(ret.len), 1, fp); ret.parts_cut = malloc(sizeof(bool) * ret.len); assert(ret.parts_cut); read = fread(&ret.parts_cut, sizeof(bool), ret.len, fp); if(read != ret.len){ fprintf(stderr, "read unexpected size of data\n"); ret.len = -1; } if (getc(fp) != EOF){ fprintf(stderr, "expected file end. something went wrong. \n"); ret.len = -1; } fclose(fp); return ret; } int main(int argc, char *argv[]){ Cutlog clog; const char* path = "testbinary"; //initialize cutlog struct clog.len = 687; clog.parts_cut = malloc(sizeof(bool) * clog.len ); assert(clog.parts_cut); for (int i = 0; i < clog.len; i++){ clog.parts_cut[i] = false; } //save to binary file and free from memory save_cutlog(path, &clog); free(clog.parts_cut); //load from binary file clog = load_cutlog(path); fprintf(stdout, "len is %d\n", clog.len); return 0; }
错误原因
load_cutlog函数中读取bool数组的fread调用参数错误:
read = fread(&ret.parts_cut, sizeof(bool), ret.len, fp);
此处传递的是&ret.parts_cut(指针变量自身的地址,类型为bool**),而非ret.parts_cut(malloc分配的数组首地址,类型为bool*)。fread会将文件中的bool数据直接写入指针变量ret.parts_cut的内存位置,覆盖malloc返回的合法地址,导致后续对该指针的访问变为非法内存操作,最终触发栈溢出。
修复方案
修正load_cutlog中的fread调用,将第一个参数改为ret.parts_cut,同时补充运行时错误处理(替换Release模式会失效的assert):
//returns cutlog with length of -1 on failure to load log Cutlog load_cutlog(const char *path){ Cutlog ret; FILE *fp; size_t read = 0; ret.len = -1; ret.parts_cut = NULL; fp = fopen(path, "rb"); if (fp == NULL) { fprintf(stderr, "Failed to open cutlog file\n"); return ret; } fseek(fp, 0, SEEK_SET); if (fread(&ret.len, sizeof(ret.len), 1, fp) != 1) { fprintf(stderr, "Failed to read length\n"); fclose(fp); ret.len = -1; return ret; } // 检查长度合法性,避免非法内存分配 if (ret.len <= 0) { fprintf(stderr, "Invalid array length\n"); fclose(fp); ret.len = -1; return ret; } ret.parts_cut = malloc(sizeof(bool) * ret.len); if (ret.parts_cut == NULL) { fprintf(stderr, "Malloc failed\n"); fclose(fp); ret.len = -1; return ret; } // 修正:传递数组首地址而非指针的地址 read = fread(ret.parts_cut, sizeof(bool), ret.len, fp); if(read != ret.len){ fprintf(stderr, "read unexpected size of data\n"); free(ret.parts_cut); ret.parts_cut = NULL; ret.len = -1; } if (getc(fp) != EOF){ fprintf(stderr, "expected file end. something went wrong. \n"); free(ret.parts_cut); ret.parts_cut = NULL; ret.len = -1; } fclose(fp); return ret; }
额外优化说明:
- 替换
assert为运行时错误判断,保证Release模式下的异常处理能力; - 增加数组长度合法性检查,避免malloc分配0或负数大小的内存;
- 错误时及时释放已分配的内存,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Sean McBride
相关产品推荐
相关产品推荐

