从对象C++中的文件读取内容时出现分段错误
Segmentation fault occur while reading content from file in object C++
在我的代码中,我首先将名称和手机号码存储在一个对象中,然后使用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<<"NamettPhone no"<<endl;
cout<<name<<"tt"<<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();
}
请帮我哪里错了。提前感谢。。。!
所以,在我给你一个解决方案之前,让我们简要谈谈这里发生了什么:
ofs.write((char*)&t1,sizeof(t1));
您正在做的是将t1强制转换为指向char的指针,并说"按原样将t1的内存表示写入ofs"。所以我们必须问问自己:t1的记忆表示是什么?
- 您正在存储一个(实现定义的,很可能是4字节)整数
- 您还存储了一个复杂的std::string对象
写入4字节整数可能没问题。它绝对不可移植(big-endian与little-endian),如果在具有不同endianness的平台上读取文件,则可能会得到错误的int。
写std::string
肯定不好。字符串是复杂的对象,它们通常在堆上分配存储(尽管有小字符串优化之类的事情)。这意味着您将序列化指向动态分配对象的指针。这永远不会起作用,因为读回指针会指向内存中您完全无法控制的某个位置。这是未定义行为的一个很好的例子。任何事情都会发生,你的程序可能会发生任何事情,包括尽管存在深层次的问题,但"看起来工作正常"。在您的特定示例中,因为创建的Telephone对象仍在内存中,所以您得到的是指向相同动态分配内存的2个指针。当temp
对象超出范围时,它会删除该内存。
当您返回到主函数时,当t1
超出范围时,它会再次尝试删除同一内存。
序列化任何类型的指针都是一个很大的禁忌。如果对象内部由指针组成,则需要制定一个自定义解决方案,说明这些指针将如何存储在流中,并在以后读取以构建新对象。一个常见的解决方案是将它们"当作"按值存储,然后,当从存储器中读取对象时,动态分配内存,并将对象的内容放在同一内存中。如果您试图序列化多个对象指向内存中同一地址的情况,这显然是行不通的:如果您尝试应用此解决方案,最终会得到原始对象的多个副本。
幸运的是,对于std::string
的情况,这个问题很容易解决,因为字符串已经重载了operator<<
和operator>>
,并且您不需要实现任何东西来使它们工作
edit:仅仅使用operator<<
和operator>>
对std::string
不起作用,稍后解释了原因
如何使其发挥作用:
有很多可能的解决方案,我将在这里分享一个。基本思想是,您应该单独序列化Telephone结构的每个成员,并依赖于每个成员都知道如何序列化自己这一事实。我将忽略跨端兼容性的问题,让答案更简单一点,但如果你关心跨平台兼容性,你应该考虑一下。
我的基本方法是覆盖类电话的operator<<
和operator>>
。
我声明两个免费函数,它们是电话类的朋友。这将允许他们探查不同电话对象的内部,以序列化其成员。
class Telephone {
friend ostream& operator<<(ostream& os, const Telephone& telephone);
friend istream& operator>>(istream& is, Telephone& telephone);
// ...
};
edit:我最初对字符串进行序列化的代码是错误的,所以我认为它相当简单的评论显然是错误的
实现这些函数的代码有一个令人惊讶的转折。由于字符串的operator>>
在遇到空白时会停止从流中读取,因此名称不是一个单词或带有特殊字符将不起作用,并使流处于错误状态,无法读取电话号码。为了解决这个问题,我以@Michael Vecsler的例子为例,显式地存储了字符串的长度。我的实现如下:
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;
}
请注意,您必须确保您写入的数据与您稍后尝试读取的数据相匹配。如果存储了不同数量的信息,或者参数的顺序错误,那么最终将不会得到有效的对象。如果您以后对Telephone类进行任何类型的修改,通过添加想要保存的新字段,您将需要修改这两个函数。
为了支持带有空格的名称,还应该修改从cin读取名称的方式。一种方法是使用std::getline(std::cin, name);
而不是cin >> name
最后,您应该如何从这些流中序列化和反序列化:不要使用ostream::write()
和istream::read()
函数,而是使用我们已经覆盖的operator<<
和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;
}
问题
不能简单地将std::string
对象转储到一个文件中。注意,std::string
被定义为
std::basic_string<char, std::char_traits<char>, std::allocator<char>>
当std::string
无法避免时,它使用std::allocator<char>
为字符串分配堆内存。通过使用ofs.write((char*)&t1,sizeof(t1))
编写Telephone
对象,您也将其包含的std::string
编写为位的集合。这些CCD_ 27比特中的一些可以是从CCD_ 28获得的指针。这些指针指向包含字符串字符的堆内存。
通过调用ofs.write()
,程序写入指针,但不写入字符。然后,当使用ifs.read()
读取字符串时,它具有指向不包含字符的未分配堆的指针。即使它指向了一个有效的堆,奇迹般的是,它仍然不会包含它应该包含的字符。有时你可能很幸运,程序不会崩溃,因为字符串足够短,可以避免堆分配,但这是完全不可靠的。
解决方案
您必须为此类编写自己的序列化代码,而不是依赖于ofs.write()
。有几种方法可以做到这一点。首先,可以使用boost序列化。您可以简单地按照链接教程中的示例进行序列化。
另一种选择是自己从头开始做每件事。当然,最好使用现有的代码(比如boost),但自己实现它可能是一种很好的学习体验。通过实现自己,你可以更好地了解boost在引擎盖下可能会做什么:
void writeData(std::ostream & out) const {
unsigned size = name.size();
out.write((char*)&size, sizeof(size));
out.write(name.data(), size);
out.write((char*)&phno, sizeof(phno));
}
然后在getData
中按相同顺序读取。当然,您必须动态地将字符串分配到正确的大小,然后用ifs.read()
填充它。
与用于字符串的operator<<
不同,此技术适用于任何类型的字符串。它可以很好地处理包含任何字符的字符串,包括空格和空字符(