Ошибка сегментации при чтении содержимого из файла в объекте C ++ - PullRequest
0 голосов
/ 07 октября 2018

Сначала в моем коде я сохранил имя и номер мобильного телефона в одном объекте, после чего записал этот объект в один текстовый файл с помощью метода fstream.write ().Он успешно работает, но когда я читаю это записанное содержимое в другой объект и вызываю метод display, тогда он правильно отображает данные, но выдает ошибку сегментации после печати данных.Вот мой код -

#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();
}

Пожалуйста, помогите мне, где я не прав.Заранее спасибо ...!

Ответы [ 2 ]

0 голосов
/ 07 октября 2018

Итак, прежде чем дать вам решение, давайте кратко поговорим о том, что здесь происходит:

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

Что вы делаете, это приведение t1 к указателю на char,и сказать «записать в оф-представление памяти t1, как есть».Таким образом, мы должны спросить себя: что это за представление в памяти t1?

  1. Вы храните целое число (определенное реализацией, скорее всего, 4 байта)
  2. Вы также храните комплексобъект std :: string.

Запись 4-байтового целого числа может быть в порядке.Он определенно не переносим (big-endian против little endian), и вы можете получить неправильное значение int, если файл читается на платформе с другим порядком байтов.

Запись std::string определенно не подходит,Строки являются сложным объектом, и они чаще всего выделяют память в куче (хотя существует такая вещь, как оптимизация небольших строк).Это означает, что вы собираетесь сериализовать указатель на динамически размещенный объект.Это никогда не будет работать, так как чтение указателя назад будет указывать на какое-то место в памяти, которое вы абсолютно не можете контролировать.Это отличный пример неопределенного поведения.Все идет, и все может случиться с вашей программой, в том числе «кажется, работает правильно», несмотря на глубоко укоренившиеся проблемы.В вашем конкретном примере, поскольку созданный объект Telephone все еще находится в памяти, вы получаете 2 указателя на одну и ту же динамически распределенную память.Когда ваш объект temp выходит из области видимости, он удаляет эту память.

Когда вы возвращаетесь к своей основной функции, когда t1 выходит из области видимости, он пытается удалить ту же самую память снова.

Сериализация любого рода указателей - это большое нет-нет.Если ваши внутренние объекты состоят из указателей, вам нужно принять решение о том, как эти указатели будут храниться в вашем потоке, а затем прочитать, чтобы создать новый объект.Распространенным решением является сохранение их «как будто», они были сохранены по значению, а затем при чтении объекта из хранилища динамически распределяют память и помещают содержимое объекта в ту же память.Это, очевидно, не будет работать, если вы попытаетесь сериализовать случай, когда несколько объектов указывают на один и тот же адрес в памяти: если вы попытаетесь применить это решение, у вас будет несколько копий вашего исходного объекта.

К счастью, для случая std::string эта проблема легко решается, поскольку строки перегружены operator<< и operator>>, и вам не нужно ничего реализовывать, чтобы заставить их работать,

edit: просто использование operator<< и operator>> не сработает для std::string, чуть позже объяснено почему.

Как сделатьэто работает:

Есть много возможных решений, и я собираюсь поделиться здесь.Основная идея заключается в том, что вы должны сериализовать каждого члена вашей структуры телефона в отдельности и полагаться на тот факт, что каждый участник знает, как сериализовать себя.Я собираюсь игнорировать проблему совместимости с перекрестным порядком байтов, чтобы сделать ответ немного более коротким, но если вы заботитесь о кросс-платформенной совместимости, вам следует подумать об этом.

Мой основной подход - переопределить operator<< и operator>> для класса телефона.

Я объявляю две бесплатные функции, которые являются друзьями класса «Телефон».Это позволило бы им тыкать во внутренние части различных телефонных объектов, чтобы сериализовать их участников.

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

edit: у меня изначально был код для сериализации строк неправильно, поэтому мой комментарий, что это довольно просто, просто неверен

Код для реализации функций имеет неожиданный поворот.Потому что 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;
}

Обратите внимание, что вы должны убедиться, что данные, которые вы пишете, соответствуют данным, которые вы собираетесь позже попытаться прочитать.Если вы храните другой объем информации, или аргументы находятся в неправильном порядке, вы не получите правильный объект.Если вы позже внесете какие-либо изменения в класс «Телефон», добавив новые поля, которые вы хотите сохранить, вам нужно будет изменить обе функции.

Для поддержки имен с пробелами также следует изменить способ чтения имен из 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;
}
0 голосов
/ 07 октября 2018

проблема

Вы не можете просто сбросить std::string объекты в файл.Обратите внимание, что std::string определяется как

std::basic_string<char, std::char_traits<char>, std::allocator<char>>

Когда std::string не может этого избежать, он использует std::allocator<char> для выделения памяти кучи для строки.Записывая свой Telephone объект с помощью ofs.write((char*)&t1,sizeof(t1)), вы также пишете std::string, который он содержит как набор битов.Некоторые из этих std::string битов могут быть указателями, полученными из std::allocator.Эти указатели указывают на кучную память, которая содержит символы строки.

При вызове ofs.write() программа записывает указатели, но не символы.Затем, когда строка читается с ifs.read(), она имеет указатели на нераспределенную кучу, которая не содержит символов.Даже если бы оно каким-то чудом указывало на действительную кучу, оно все равно не содержало бы символов, которые должны были быть.Иногда вы можете быть счастливчиком , и программа не будет аварийно завершать работу, потому что строки были достаточно короткими, чтобы избежать выделения кучи, но это совершенно ненадежно.

решение

Вы должны написать собственный код сериализации для этого класса, а не полагаться на ofs.write().Есть несколько способов сделать это.Прежде всего, вы можете использовать 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<< для строк, этот метод хорошо работает для любого типа строки.Он будет хорошо работать со строками, которые содержат любой символ, включая пробелы и нулевые символы (\0).Техника operator>> не будет работать со строками, в которых есть пробелы, такие как комбинации имени и фамилии, поскольку она ограничивается пробелами.


Обратите внимание, что существуют специализированные распределители, которые делаютсериализация / десериализация тривиальна.Такой распределитель может выделять строку из заранее выделенного буфера и использовать причудливые указатели (такие как boost :: interprocess :: offset_ptr ) в этот буфер.Тогда можно просто выгрузить весь буфер и перечитать его позже без проблем.По какой-то причине этот подход обычно не используется.


Критично:

Безопасность является проблемой.Если данные не находятся под вашим контролем, то они могут быть использованы для взлома вашей системы.В моем примере сериализации хуже всего может быть нехватка памяти.Недостаточно памяти может быть вектором атаки для отказа в обслуживании или, что еще хуже.Возможно, вам следует ограничить максимальный размер строки и устранить ошибку.

Еще одна вещь, которую следует учитывать, - это взаимодействие между системами.Не все системы представляют int или long одинаково.Например, в 64-разрядной версии linux long составляет 8 байт, а в MS-Windows - 4 байта.Самое простое решение - использовать out<<size<<' ' для записи размера, но обязательно используйте локаль C, в противном случае длина из четырех цифр может содержать запятые или точки, что испортит синтаксический анализ.

...