Windows下保存unsigned char*到文件并读取出现异常的问题
问题描述
我们使用GLAD与GLFW开发OpenGL纹理加载功能,以下代码在Ubuntu系统中运行正常:
#include <vector> #include <iostream> #include <fstream> #include <cstring> using namespace std; #include <glad/glad.h> #include <GLFW/glfw3.h> #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" const bool loadResavedImages = true; GLFWwindow* window; void loadTexture(char const* filename, unsigned int& texture) { cout << filename << endl; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = NULL; char newFileName[50]; snprintf(newFileName, sizeof(char) * (strlen(filename) - 3), "%s", filename); ifstream readFile(newFileName); if (loadResavedImages && readFile) { if (readFile.is_open()) { readFile >> width >> height; data = new unsigned char[width * height * 4]; readFile.read((char*)data, width * height * 4); } else { cout << "shouldnt happendfklgdskf" << endl; } readFile.close(); } else { readFile.close(); data = stbi_load(filename, &width, &height, &nrChannels, 0); ofstream saveFile(newFileName); if (saveFile.is_open()) { saveFile << width << " " << height; saveFile.write((char*)&data[0], width * height * 4); } else cout << "WHY!?!?!SDSGGFGAGSSG" << endl; saveFile.close(); } for (int i = 0; i < width * height * 4; i++) { cout << int(data[i]) << " "; } if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "failed to load texture " << filename << endl; exit(0); } if (loadResavedImages && readFile) delete[] data; else { stbi_image_free(data); } } int main() { glfwInit(); window = glfwCreateWindow(500, 500, "Window", NULL, NULL); glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to initialize GLAD" << endl; return 0; } unsigned int texture; loadTexture("twirl.png", texture); return 0; }
当加载一张400×400、大部分区域为透明空白的twirl.png纹理时:
- Ubuntu下输出的纹理数据末尾有预期的大量0;将纹理数据保存至
twirl文件后重新加载,结果与原数据一致。 - Windows系统中运行时,重新加载后的纹理数据末尾出现大量十进制205(十六进制
0xCD),而非预期的0。
异常原因
文件模式差异:
Linux下fstream默认以二进制模式打开文件,而Windows下默认是文本模式。文本模式会自动处理换行符:- 写入时,将
0x0A(换行)转换为0x0D0xA(回车+换行),导致二进制数据文件体积变大。 - 读取时,会把
0x0D0xA转换回0x0A,同时如果遇到0x1A(ASCII EOF字符)会提前终止读取,导致实际读取的字节数不足。未被覆盖的data数组区域保留了Windows堆内存的默认填充值0xCD(十进制205)。
- 写入时,将
文件名处理逻辑不安全:
原代码用strlen(filename)-3来截断文件名后缀,这种写法仅适用于后缀为3个字符的文件(如.png),如果文件名后缀长度不符或文件名长度不足3,会导致缓冲区溢出或截断错误。
修复方案
关键改动点:
- 打开文件时强制指定二进制模式,避免文本模式对二进制数据的篡改。
- 修复文件名截断逻辑,用更安全的方式去掉文件后缀。
- 增加读取字节数校验,确保纹理数据完整读取。
- 强制
stbi_load加载为RGBA格式,避免通道数不确定的问题。
修改后的代码:
#include <vector> #include <iostream> #include <fstream> #include <cstring> #include <limits> using namespace std; #include <glad/glad.h> #include <GLFW/glfw3.h> #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" const bool loadResavedImages = true; GLFWwindow* window; void loadTexture(char const* filename, unsigned int& texture) { cout << filename << endl; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = NULL; // 安全生成无后缀文件名 char newFileName[50]; const char* dot = strrchr(filename, '.'); if (dot != nullptr && dot > filename) { size_t nameLen = dot - filename; strncpy(newFileName, filename, nameLen); newFileName[nameLen] = '\0'; } else { strncpy(newFileName, filename, sizeof(newFileName) - 1); newFileName[sizeof(newFileName) - 1] = '\0'; } // 二进制模式打开读取文件 ifstream readFile(newFileName, ios::binary); if (loadResavedImages && readFile.is_open()) { readFile >> width >> height; // 跳过宽高后的空白字符,避免干扰二进制读取 readFile.ignore(numeric_limits<streamsize>::max(), '\n'); data = new unsigned char[width * height * 4]; readFile.read((char*)data, width * height * 4); // 校验读取的字节数 if (readFile.gcount() != width * height * 4) { cout << "Error: Incomplete texture data read" << endl; delete[] data; readFile.close(); exit(0); } readFile.close(); } else { if (readFile.is_open()) readFile.close(); // 强制加载为RGBA格式,确保数据为4通道 data = stbi_load(filename, &width, &height, &nrChannels, 4); if (!data) { cout << "failed to load texture " << filename << endl; exit(0); } // 二进制模式打开写入文件 ofstream saveFile(newFileName, ios::binary); if (saveFile.is_open()) { saveFile << width << " " << height << "\n"; saveFile.write((char*)data, width * height * 4); } else { cout << "Failed to open save file" << endl; stbi_image_free(data); exit(0); } saveFile.close(); } if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } // 释放资源 if (loadResavedImages && data) delete[] data; else stbi_image_free(data); } int main() { glfwInit(); window = glfwCreateWindow(500, 500, "Window", NULL, NULL); if (!window) { cout << "Failed to create GLFW window" << endl; glfwTerminate(); return 0; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to initialize GLAD" << endl; glfwTerminate(); return 0; } unsigned int texture; loadTexture("twirl.png", texture); // 主循环,避免程序直接退出 while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; }
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

