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

C++含vector的结构体(struct)文件存读崩溃问题求助

问题:包含std::vector的结构体读写文件后,访问vector导致程序崩溃

我是C++新手,已掌握C#基础语法(使用近一年),还有2年PHP和Python使用经验。尝试将包含vector的struct写入文件并后续读取,结构体定义如下:

struct inventoryItem
{
    std::string itemName;
    bool unlocked{false};
};
struct saveData
{
    std::string userName{"John"};
    int hunger{100};
    int health{100};
    int money{5};
    std::vector<inventoryItem> inventory;
};

保存和读取代码如下:

void saveGame::createSave()
{
    saveData saveObj;
    inventoryItem newItem;

    newItem.itemName = "shoes";
    newItem.unlocked = true;
    saveObj.inventory.push_back(newItem);

    newItem.itemName = "knife";
    saveObj.inventory.push_back(newItem);

    save(saveObj);
    return;
}

void saveGame::save(saveData toSave)
{
    std::fstream fileW(saveGame::fileName, std::ios_base::in);

    if (!fileW.good() || !fileW.is_open())
    {
        system("exit");
    } else
    {
        fileW.write((char *) &toSave, sizeof(saveData));
        fileW.close();
    }
    return;
}
saveGame::saveData saveGame::getSave()
{
    std::fstream fileR(saveGame::fileName, std::ios_base::out);
    saveData toRetrieve;

    if (!fileR.good() || !fileR.is_open())
        system("exit");
    else
    {
        fileR.read((char *) &toRetrieve, sizeof(saveData));
    }

    return toRetrieve;
}

测试代码:

saveGame::saveData data = saveObj.getSave();
std::cout<<data.userName<<std::endl;
std::cout<<data.health<<std::endl;
std::cout<<data.inventory[0].itemName;

运行现象:除vector内的结构体数据外,其他数据均可正常访问,但一旦涉及vector相关数据,程序就无报错崩溃。

  • 期望输出:
*CONSOLE OUTPUT*
John //username
100 // health
shoes // inventory.itemName (string)
true // inventory.unlocked (bool)
knife // inventory.itemName (string)
false // inventory.unlocked (bool)
  • 实际输出:
*CONSOLE OUTPUT*
John //username
100 // health

输出后程序直接崩溃。


崩溃原因

你当前用fileW.write((char *)&toSave, sizeof(saveData))直接将结构体的内存二进制写入文件,但std::vector和std::string属于非值类型容器:它们内部仅存储指针(指向实际数据的内存地址)、大小、容量等元数据,真正的字符串内容、vector元素都存放在堆内存的其他区域。

举个简单例子:std::string userName里并没有直接存储"John"字符,而是存了一个指针,指向堆内存中存放"John"的地址。当你把整个结构体写入文件时,保存的只是这个指针的数值,而非"John"本身。

当你从文件读回结构体时,这个指针指向的内存地址早已失效(程序重启后原内存已被回收或占用),此时访问inventory[0].itemName就是在读取无效内存,直接触发程序崩溃。

另外代码还有两个基础错误:

  • 保存文件时用了std::ios_base::in(只读模式),根本无法写入文件,应改为std::ios_base::out | std::ios_base::binary
  • 读取文件时用了std::ios_base::out(只写模式),根本无法读取文件,应改为std::ios_base::in | std::ios_base::binary

解决方案:手动序列化结构体成员

既然直接读写内存不可行,那就手动将每个成员的实际数据按顺序写入文件,读取时再按相同顺序解析还原。

1. 修改保存函数(save)

void saveGame::save(saveData toSave)
{
    // 二进制写模式打开文件,不存在则创建,存在则覆盖
    std::ofstream fileW(saveGame::fileName, std::ios_base::out | std::ios_base::binary);
    if (!fileW.is_open())
    {
        std::cerr << "无法打开保存文件!" << std::endl;
        return;
    }

    // 保存std::string:先存字符串长度,再存实际字符
    size_t nameLen = toSave.userName.size();
    fileW.write((char*)&nameLen, sizeof(nameLen));
    fileW.write(toSave.userName.c_str(), nameLen);

    // 保存int类型基础数据
    fileW.write((char*)&toSave.hunger, sizeof(toSave.hunger));
    fileW.write((char*)&toSave.health, sizeof(toSave.health));
    fileW.write((char*)&toSave.money, sizeof(toSave.money));

    // 保存vector:先存元素个数,再逐个保存每个inventoryItem
    size_t itemCount = toSave.inventory.size();
    fileW.write((char*)&itemCount, sizeof(itemCount));
    for (const auto& item : toSave.inventory)
    {
        // 保存item的itemName
        size_t itemNameLen = item.itemName.size();
        fileW.write((char*)&itemNameLen, sizeof(itemNameLen));
        fileW.write(item.itemName.c_str(), itemNameLen);
        // 保存item的unlocked布尔值
        fileW.write((char*)&item.unlocked, sizeof(item.unlocked));
    }

    fileW.close();
}

2. 修改读取函数(getSave)

saveGame::saveData saveGame::getSave()
{
    saveData toRetrieve;
    // 二进制读模式打开文件
    std::ifstream fileR(saveGame::fileName, std::ios_base::in | std::ios_base::binary);
    if (!fileR.is_open())
    {
        std::cerr << "无法打开读取文件!" << std::endl;
        return toRetrieve; // 返回默认初始化的结构体
    }

    // 读取userName:先读长度,再读字符
    size_t nameLen;
    fileR.read((char*)&nameLen, sizeof(nameLen));
    toRetrieve.userName.resize(nameLen);
    fileR.read(&toRetrieve.userName[0], nameLen);

    // 读取int类型基础数据
    fileR.read((char*)&toRetrieve.hunger, sizeof(toRetrieve.hunger));
    fileR.read((char*)&toRetrieve.health, sizeof(toRetrieve.health));
    fileR.read((char*)&toRetrieve.money, sizeof(toRetrieve.money));

    // 读取vector:先读元素个数,再逐个读取inventoryItem
    size_t itemCount;
    fileR.read((char*)&itemCount, sizeof(itemCount));
    toRetrieve.inventory.reserve(itemCount); // 预分配空间提升效率
    for (size_t i = 0; i < itemCount; ++i)
    {
        inventoryItem item;
        // 读取itemName
        size_t itemNameLen;
        fileR.read((char*)&itemNameLen, sizeof(itemNameLen));
        item.itemName.resize(itemNameLen);
        fileR.read(&item.itemName[0], itemNameLen);
        // 读取unlocked
        fileR.read((char*)&item.unlocked, sizeof(item.unlocked));
        toRetrieve.inventory.push_back(item);
    }

    fileR.close();
    return toRetrieve;
}

3. 修改测试代码

现在可以正常遍历vector输出所有内容:

saveGame::saveData data = saveObj.getSave();
std::cout << data.userName << std::endl;
std::cout << data.health << std::endl;
for (const auto& item : data.inventory)
{
    std::cout << item.itemName << std::endl;
    std::cout << std::boolalpha << item.unlocked << std::endl; // std::boolalpha让bool输出true/false而非1/0
}

额外说明

和你熟悉的C#不同,C没有内置的对象序列化机制(类似.NET的BinaryFormatter),所以需要手动处理每个成员。如果后续开发大型项目,可以使用Boost.Serialization这类第三方库简化序列化流程,但新手先掌握手动序列化逻辑,能帮你更深刻理解C的内存模型。

内容的提问来源于stack exchange,提问作者NikkieDev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 06:40:43