You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

异常原因

  1. 文件模式差异:
    Linux下fstream默认以二进制模式打开文件,而Windows下默认是文本模式。文本模式会自动处理换行符:

    • 写入时,将0x0A(换行)转换为0x0D0xA(回车+换行),导致二进制数据文件体积变大。
    • 读取时,会把0x0D0xA转换回0x0A,同时如果遇到0x1A(ASCII EOF字符)会提前终止读取,导致实际读取的字节数不足。未被覆盖的data数组区域保留了Windows堆内存的默认填充值0xCD(十进制205)。
  2. 文件名处理逻辑不安全:
    原代码用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 15:42:33