从对象 C++ 中的文件读取内容时发生分段错误

Roh*_*har 4 c++ file-io fstream file c++14

在我的代码中,我首先将姓名和手机号码存储在一个对象中,然后使用 fstream.write() 方法将该对象写入一个文本文件。它成功地工作,但是当我将写入的内容读入另一个对象并调用显示方法时,它会正确显示数据,但是在打印数据后却给了我分段错误。这是我的代码 -

#include<iostream>
#include<fstream>
using namespace std;
class Telephone
{
private:
    string name="a";
    int phno=123;
public:
    void getTelephoneData()
    {
        cout<<"Enter Name:";
        cin>>name;
        cout<<"Enter Phone Number:";
        cin>>phno;
    }
    void displayData()
    {
        cout<<"Name\t\tPhone no"<<endl;
        cout<<name<<"\t\t"<<phno<<endl;
    }

    void getData() {
        Telephone temp;
        ifstream ifs("Sample.txt",ios::in|ios::binary);
        ifs.read((char*)&temp,sizeof(temp));
        temp.displayData();
    }   
};
int main()
{
    Telephone t1;
    t1.getTelephoneData();
    cout<<"----Writing Data to file------"<<endl;
    ofstream ofs("Sample.txt",ios::out|ios::binary);
    ofs.write((char*)&t1,sizeof(t1));
    ofs.close();
    t1.getData();
}
Run Code Online (Sandbox Code Playgroud)

请帮助我我错的地方。提前致谢...!

div*_*nas 6

所以,在我给你解决方案之前,让我们简单地谈谈这里发生了什么:

ofs.write((char*)&t1,sizeof(t1));

您正在做的是将 t1 转换为指向 char 的指针,并说“按原样写入 t1 的内存表示”。所以我们必须问自己:t1 的这个内存表示是什么?

  1. 您正在存储一个(实现定义,很可能是 4 字节)整数
  2. 您还存储了一个复杂的 std::string 对象。

写入 4 字节整数可能没问题。它绝对不是可移植的(大端与小端),如果在具有不同端序的平台上读取文件,您最终可能会得到错误的 int。

写的std::string肯定不行。字符串是复杂的对象,它们最常在堆上分配存储空间(尽管存在诸如小字符串优化之类的东西)。这意味着您将序列化一个指向动态分配对象的指针。这永远不会起作用,因为读回指针将指向内存中您完全无法控制的某个位置。这是未定义行为的一个很好的例子。任何事情都会发生,并且您的程序可能会发生任何事情,包括尽管存在根深蒂固的问题,但“似乎可以正常工作”。在您的具体示例中,由于创建的 Telephone 对象仍在内存中,因此您得到的是 2 个指向同一动态分配内存的指针。当您的temp对象超出范围时,它会删除该内存。

当您返回主函数时t1,超出范围时,它会再次尝试删除相同的内存。

序列化任何类型的指针都是一个很大的禁忌。如果您的对象内部由指针组成,您需要针对这些指针将如何存储在您的流中的方式制定自定义解决方案,然后读取以构造一个新对象。一个常见的解决方案是将它们“像”按值存储一样存储,然后在从存储中读取对象时,动态分配内存并将对象的内容放在同一内存中。如果您尝试序列化多个对象指向内存中相同地址的情况,这显然不起作用:如果您尝试应用此解决方案,您最终会得到原始对象的多个副本。

幸运的是,对于 a 的情况,std::string这个问题很容易解决,因为字符串已经重载operator<<operator>>,并且您不需要实现任何东西来使它们工作。

编辑:只是使用operator<<并且operator>>不会为 工作std::string,稍后解释原因。

如何使它工作:

有很多可能的解决方案,我将在这里分享一个。基本思想是您应该单独序列化 Telephone 结构的每个成员,并依赖于每个成员都知道如何序列化自身的事实。我将忽略跨字节顺序兼容性问题,使答案更简洁一些,但如果您关心跨平台兼容性,则应该考虑一下。

我的基本方法是覆盖operator<<operator>>为班级电话。

我声明了两个免费函数,它们是 Telephone 类的朋友。这将允许他们查看不同电话对象的内部结构以序列化其成员。

class Telephone { 
   friend ostream& operator<<(ostream& os, const Telephone& telephone);
   friend istream& operator>>(istream& is, Telephone& telephone);
   // ... 
};
Run Code Online (Sandbox Code Playgroud)

编辑:我最初有错误的序列化字符串的代码,所以我的评论是相当简单的,这是完全错误的

实现这些功能的代码有一个令人惊讶的扭曲。因为 operator>>字符串在遇到空格时停止从流中读取,名称不是单个单词或带有特殊字符的名称将不起作用,并使流处于错误状态,无法读取电话号码。为了解决这个问题,我遵循了@Michael Veksler 的例子,并显式存储了字符串的长度。我的实现如下所示:

ostream& operator<<(ostream& os, const Telephone& telephone)
{
    const size_t nameSize = telephone.name.size();
    os << nameSize;
    os.write(telephone.name.data(), nameSize);
    os << telephone.phno;
    return os;
}

istream& operator>>(istream& is, Telephone& telephone)
{
    size_t nameSize = 0;
    is >> nameSize;
    telephone.name.resize(nameSize);
    is.read(&telephone.name[0], nameSize);
    is >> telephone.phno;
    return is;
}
Run Code Online (Sandbox Code Playgroud)

请注意,您必须确保您写入的数据与您稍后将尝试读取的数据相匹配。如果您存储不同数量的信息,或者参数的顺序错误,您将不会得到一个有效的对象。如果您稍后对 Telephone 类进行任何类型的修改,通过添加您想要保存的新字段,您将需要修改这两个函数。

为了支持其中包含空格的名称,您从 cin 读取名称的方式也应该修改。一种方法是使用std::getline(std::cin, name);而不是cin >> name

最后,您应该如何从这些流中序列化和反序列化:不要使用ostream::write()andistream::read()函数 - 而是使用我们已经覆盖的operator<<and operator>>

void getData() {
    Telephone temp;
    ifstream ifs("Sample.txt",ios::in|ios::binary);
    ifs >> temp;
    temp.displayData();
} 

void storeData(const Telephone& telephone) {
    ofstream ofs("Sample.txt",ios::out|ios::binary);
    ofs << telephone;
}
Run Code Online (Sandbox Code Playgroud)