C语言读取Shader文件时fgets触发内存访问违例错误排查
解决Shader文件读取时的内存访问违例问题
错误信息:在Project-512.exe进程的ucrtbase.dll模块中,地址0x00007FFECFCBB16B处抛出异常:0xC0000005: 写入地址0xFFFFFFFFF9CF4880时发生内存访问违例。
核心问题分析
错误触发在readShader函数的while (fgets(str, fileSize, fpointer) != NULL)行,根源在于以下几个代码缺陷:
- 缓冲区参数错误:
fgets的第二个参数需包含字符串终止符\0的空间,你传入的fileSize是文件字节数,未预留终止符位置,导致写入越界。 - 类型溢出风险:
ftell返回long类型,用int存储fileSize会在大文件场景下溢出,分配的内存远小于实际需求。 - 无效指针访问:
fgets返回NULL时,str的内容未定义,后续printf("%s", str)会访问非法内存。 - 内存泄漏与无效返回:分配的
shaderCode未写入任何数据就返回,同时str的内存未释放,造成泄漏。 - 双重释放问题:
compileShaders中重复释放已被释放的shader源指针,触发未定义行为。
修复后的代码实现
修正后的readShader函数
static char* readShader(const char path[]) { char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { printf("Current working dir: %s\n", cwd); } else { perror("getcwd() error"); return NULL; } char full_path[512]; snprintf(full_path, sizeof(full_path), "%s/%s", cwd, path); FILE* fpointer = fopen(full_path, "rb"); // 二进制模式避免换行符转换问题 if (fpointer == NULL) { fprintf(stderr, "ERROR: COULD NOT LOAD SHADER: %s\n", full_path); return NULL; } // 获取文件大小 fseek(fpointer, 0, SEEK_END); long fileSize = ftell(fpointer); if (fileSize < 0) { fprintf(stderr, "ERROR: Failed to get file size\n"); fclose(fpointer); return NULL; } rewind(fpointer); // 分配内存,预留终止符空间 char* shaderCode = malloc(fileSize + 1); if (shaderCode == NULL) { fprintf(stderr, "Memory allocation failed\n"); fclose(fpointer); return NULL; } // 一次性读取整个文件,避免逐行读取的边界问题 size_t bytesRead = fread(shaderCode, 1, fileSize, fpointer); if (bytesRead != fileSize) { fprintf(stderr, "ERROR: Failed to read entire shader file\n"); free(shaderCode); fclose(fpointer); return NULL; } // 添加字符串终止符 shaderCode[fileSize] = '\0'; printf("Shader loaded successfully. File size: %ld bytes\n", fileSize); printf("Shader content:\n%s\n", shaderCode); fclose(fpointer); return shaderCode; }
其他必要修复
- 删除
compileShaders中的重复释放:移除函数末尾的free(vertexShaderSource);和free(fragmentShaderSource);,这两个指针已在编译后释放。 - 统一文件大小类型:所有存储文件大小的变量改用
long类型,避免溢出。 - 二进制模式打开文件:用
"rb"模式读取,避免Windows下文本模式将\n转换为\r\n导致的大小计算偏差。
修复原理
- 改用一次性读取整个文件的方式,彻底规避逐行读取的缓冲区边界问题,同时提升效率。
- 确保内存分配预留字符串终止符空间,杜绝越界写入。
- 二进制模式读取保证文件大小计算准确,避免换行符转换导致的内容长度不符。
- 清理无效指针访问和重复释放操作,消除未定义行为。
内容的提问来源于stack exchange,提问作者Aegis
相关产品推荐
相关产品推荐

