C++程序首次运行正常,第二次读取已有文件时崩溃如何解决
崩溃根因
这是C二进制序列化的典型错误写法,首次运行不崩溃纯属运气,二次启动崩溃是必然结果,核心问题是你直接读写非POD类型的原始内存,完全违反C对象模型规则:
- 直接读写
std::vector的内存:std::vector本身只存三个指向堆内存的指针(起始地址、已用末尾、容量末尾),实际的学生元素存在堆上。你写文件时只存了这三个指针的数值,根本没有保存真实的学生数据。首次运行写完立刻读取,指针指向的堆内存还没释放,所以看起来能正常运行;二次启动时读回来的是上一次进程的指针地址,在当前进程中属于非法地址,访问直接触发段错误。 - 直接读写包含
std::string的TStudent对象内存:std::string和vector逻辑类似,内部同样存指向堆内存的指针,dump对象内存只会保存指针值,不会保存实际的字符串内容,读回的指针同样是非法地址。 - 保存文件误用
ios::app追加模式:首次运行已经写入了一组无效的指针数据,二次启动会再追加一组新的指针数据,读取时只会读到最早的无效指针,进一步放大问题。
修复方案
你需要自行实现合法的序列化逻辑,写入实际的业务数据而非对象内存地址,核心修改点如下:
- 改写
salveaza保存逻辑:- 废弃
ios::app模式,根据需求选ios::trunc覆盖写入全量数据 - 先写入当前学生总数,再逐个遍历学生,依次写入名字、姓氏、年龄的实际内容
- 废弃
- 改写
incarca加载逻辑:- 先读取学生总数,再循环对应次数,每次读取名字、姓氏、年龄后构造
TStudent对象,添加到vector中
- 先读取学生总数,再循环对应次数,每次读取名字、姓氏、年龄后构造
修改后的核心代码参考:
// 序列化写入 void salveaza(string file_name) { ofstream file1(file_name, ios::trunc | ios::binary); if (!file1) { cout << "文件打开失败" << endl; return; } // 先写元素总数 size_t count = students.size(); file1.write((char*)&count, sizeof(count)); for (const auto& s : students) { // 写字符串长度 + 实际内容 size_t name_len = s.Name.size(); file1.write((char*)&name_len, sizeof(name_len)); file1.write(s.Name.data(), name_len); size_t surname_len = s.Surname.size(); file1.write((char*)&surname_len, sizeof(surname_len)); file1.write(s.Surname.data(), surname_len); // 写年龄 file1.write((char*)&s.Age, sizeof(s.Age)); } file1.close(); cout << "\n文件保存成功.\n" << endl; } // 反序列化读取 void incarca(string file_name) { ifstream file2(file_name, ios::binary); if(!file2) { cout << "文件打开失败."; return; } students.clear(); // 先读元素总数 size_t count = 0; file2.read((char*)&count, sizeof(count)); for (size_t i = 0; i < count; i++) { // 读名字 size_t name_len = 0; file2.read((char*)&name_len, sizeof(name_len)); string name(name_len, '\0'); file2.read(&name[0], name_len); // 读姓氏 size_t surname_len = 0; file2.read((char*)&surname_len, sizeof(surname_len)); string surname(surname_len, '\0'); file2.read(&surname[0], surname_len); // 读年龄 int age = 0; file2.read((char*)&age, sizeof(age)); // 构造对象添加到列表 students.emplace_back(name, surname, age); } cout << "文件加载成功.\n" << endl; }
另外补充:TStudent里的aux1、aux2字段完全无用,sizeof(std::string)是固定的(等于内部指针的大小)和实际存储的字符串长度无关,可以直接删除。
内容的提问来源于stack exchange,提问作者Vlad
相关产品推荐
相关产品推荐

