Форум программистов, компьютерный форум CyberForum.ru

Запись и чтение объектов разных классов в один файл - C++

Восстановить пароль Регистрация
 
Рейтинг: Рейтинг темы: голосов - 18, средняя оценка - 4.67
moskitos80
 Аватар для moskitos80
39 / 39 / 0
Регистрация: 04.10.2011
Сообщений: 128
17.08.2012, 14:01     Запись и чтение объектов разных классов в один файл #1
Здравствуйте. Изучаю С++ по Лафоре. Дошёл до места, где объясняется, как записывать объекты разных классов в один бинарник.

У Лафоре, идея чтения из одного файла, данных объектов разных классов, следующая: перед записью данных объекта записываем его тип (в приведённом листинге типы содержит перечисление Ctypes), а при чтении - сначала читаем тип, создаём объект нужного класса и читаем в него данные.

Почитал тему - понял - начал делать: облом, прога вылетает с ошибкой сегментации памяти. В чём дело не могу понять - хоть убейте! Который день ломаю голову сегодня дошёл до того, что решил делать дампы памяти (сам я PHP - шник для меня это дело новое - дикое )

Описание проблемы следующее - есть Базовый класс: Person с парой полей (чисто для того, что бы было, что в файл записывать), и есть 2 его наследника: Student и Employee тоже со своими полями (что бы в бинарнике их байты хоть как то различались), а так же есть класс Storage, который должен их сохранять и читать из файла. Так вот, если прога отрабатывает одним запуском, т.е. пишет в файл, а потом сразу читает - всё ок! НО, если я сначала пишу в файл (комментирую строку storage.readAll(); ) а потом только читаю из файла (комментирую логику записи) то прога вылетает с ошибкой сегментации.

При этом я заметил, когда в методе storage.readAll() - инициализируется нужный обект он имеет отличающийся набор байтов от того, который в него считыватся из файла. При этом отличается только первый байт данных объекта! И почему-то всегда на 20(HEX)! Повторюсь, что при запуске программы на чтение и на запись данные при инициализации и те, что в файле абсолютно идентичны. Люди знает кто нибудь что это за фича? Подскажите чайнику.

Листинг:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>
#include <fstream>
#include <typeinfo>
 
using namespace std;
 
const int MAX_PERS = 4;
const char* PERS_FILE_NAME = "PERS_DATA.ART";
 
// Типы данных объектов:
enum Ctypes {
    Tstudent,
    Temployee
};
 
/////////////////////////////////////////////////////////////////////////////////
class Person
{
    public :
        Person() {
            this->age = 500;
        }
        virtual void getData(void)
        {
            cout  << endl << "Age  " << this->age  << endl;
        }
    protected :
        int age;
};
/////////////////////////////////////////////////////////////////////////////////
class Student : public Person
{
    public :
        Student() : Person(), gpa (13.6), course (8) {}
 
        void getData(void)
        {
            cout << endl << "---------- Student data: ----------" << endl;
            Person::getData();
            cout << endl << "GPA "     << this->gpa
                 << endl << "Course  " << this->course  << endl;
        }
    protected :
        float gpa;
        int course;
};
/////////////////////////////////////////////////////////////////////////////////
class Employee : public Person
{
    public :
        Employee() : Person(), post (555) {}
 
        void getData(void)
        {
            cout << endl << "---------- Employee data: ----------" << endl;
            Person::getData();
            cout << endl << "Post "     << this->post << endl;
        }
    protected :
        int post;
};
/////////////////////////////////////////////////////////////////////////////////
class Storage
{
    public :
        bool store(Person** pers1, int cnt)
        {
            Ctypes type;
            int size = 0;
 
            ofstream outf(PERS_FILE_NAME, ios::binary|ios::app);
            if (!outf) {cerr << endl << "Err open!"; return false;}
 
            for(int i = 0; i < cnt; i++) {
 
                type = this->getType(pers1[i]);
                outf.write((char*)&type, sizeof(type));
                if (!outf) {cerr << endl << "Err write type!"; return false;}
 
                if (type == Tstudent) {
                    size = sizeof(Student);
                } else {
                    size = sizeof(Employee);
                }
 
                outf.write((char*)pers1[i], size);
                if (!outf) {cerr << endl << "Err write type!"; return false;}
            }
            return true;
        }
 
        bool readAll()
        {
            Person* pers1[MAX_PERS];
 
            int size = 0, cnt = 0;
            Ctypes type;
 
            ifstream inf(PERS_FILE_NAME, ios::binary);
            if (!inf) {cerr << endl << "Err open!"; return false;}
 
            while (1) {
                inf.read((char*)&type, sizeof(type));
                if(inf.eof()) {break;}
 
                if (type == Tstudent) {
                    // Инициализация 
                    pers1[cnt] = new Student; // дамп: 28 2c 47 00 f4 01 00 00|9a 99 59 41 08 00 00 00
                    size = sizeof(Student);
                } else {
                    // Инициализация 
                    pers1[cnt] = new Employee; 
                    size = sizeof(Employee);
                }
                // Считывается из файла: 48 2c 47 00 f4 01 00 00|9a 99 59 41 08 00 00 00
                inf.read((char*)pers1[cnt], size); 
                if (!inf) {cerr << endl << "Err read type!"; return false;}
                pers1[cnt++]->getData();
            }
            return true;
        }
 
        Ctypes getType(Person* person)
        {
            if (typeid(*person) == typeid(Student)) {
                return Tstudent;
            }
            return Temployee;
        }
};
/////////////////////////////////////////////////////////////////////////////////
 
int main()
{
    Storage storage;
    Person* pers[MAX_PERS];
 
    // Инициализируем массив
    for (int i = 0; i < MAX_PERS; i++) {
        pers[i] = NULL;
    }
    // Тупо ручками:
    pers[0] = new Student;
    pers[1] = new Student;
    pers[2] = new Employee;
    pers[3] = new Employee;
 
    // Пишем
    if (storage.store(pers, MAX_PERS)) {
        cout << endl << "STORED OK" << endl;
    }
    // Читаем
    storage.readAll();
 
    return 0;
}
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
20.08.2012, 21:53     Запись и чтение объектов разных классов в один файл #2
moskitos80, особо в коде не разбирался, но увидел там наличие виртуальных функций, а также логику чтения/записи, основанную на сыром чтении/записи объекта из/в файл (по сути в файл записывается бинарный слепок объекта). Так вот, это наводит на следующие мысли:
Поскольку классы, объекты которых вы пишите-читаете, участвуют в полиморфной иерархии (используют виртуальные функции), каждый из таких объектов содержит в себе дополнительный указатель - адрес таблицы виртуальных функций для типа данного объекта. Именно на основе этого адреса и соответствующего смещения в таблице адрес необходимой функции вычисляется во время выполнения [реализация динамического полиморфизма стандартом не описывается, но обрисованная мной схема используется практически всегда, и не только в С++].
Теперь далее. Логика подсказывает, что таблица виртуальных функция генерируется на стадии компиляции единожды (поскольку сама по себе статична и известна компилятору на момент компиляции), и адрес её вычисляется единожды на стадии компиляции, а то, что в современных ОС используются виртуальные адреса, наводит на мысль, что теоретически таблица виртуальных функций при каждом запуске должна лежать по одному и тому-же (виртуальному) адресу, и, следовательно, такая сырая запись-чтение не должны ломать объект. Однако факты говорят, что при считывании объекта, вероятнее всего, происходит поломка за счёт того, что считанный адрес таблицы виртуальных функций не совпадает с реальным и указывает непонятно куда (возможно, за границы доступной приложению памяти, за счёт чего и вылазит сигфолт). При попытке вызвать виртуальный метод адрес берётся не из реальной таблицы, а из мусорной памяти, которая интерпретируется как таблица, и в результате программа падает либо при попытке обратиться по косвенности к таблице, либо при попытке выполнить как код функции некий мусор, который там оказался.
Итог: сырое чтение/запись в данном случае не подходят. Необходима более сложная логика сериализации. Таким образом, единый класс для чтения/записи уже не подойдёт, каждый конкретный объект должен уметь читать себя из файла/писать себя в файл (единый класс может иметь место, но он всё равно будет использовать реализацию чтения/записи, предоставляемую полиморфно реальным объектом).
Всё это только в теории, вполне возможно, что на самом деле у вас просто криво происходит запись или чтение, что и ломает рантайм, но, не разбираясь подробно в коде и просто чуть-чуть подумав, в голове появляется именно такая картинка.
moskitos80
 Аватар для moskitos80
39 / 39 / 0
Регистрация: 04.10.2011
Сообщений: 128
20.08.2012, 22:04  [ТС]     Запись и чтение объектов разных классов в один файл #3
silent_1991 - Огромное спасибо за столь развёрнутый ответ. Мне так же подсказали в другом месте, что когда я комментирую логику записи и пытаюсь только считывать записанное - т.е. фактически изменяю код листинга, то да - сбиваются адреса указателей на таблицу виртуальных ф-ций. Значение этих невидимых указателей и есть первый байт области данных слепка объекта. Пока не знаю так это или нет, но по возвращению из отпуска попробую обязательно на практике и отпишусь, потому как возможно другим тоже будет интересен итог.
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
20.08.2012, 22:07     Запись и чтение объектов разных классов в один файл #4
moskitos80, настораживает в этой всей картине только то, что по факту таблицы виртуальных функций действительно должны при каждом запуске лежать по одним и тем же адресам...
Только одно эту версию и спасает: если код компилируется в Debug-режиме (например, в Visual Studio), для большей информативности отладочных сообщений всё это действительно может формироваться в динамике (и содержать дополнительную отладочную информацию). Попробуйте этот код откомпилировать в Release-режиме (если это уже не было сделано) и проверьте всё снова.
Удачного отпуска
moskitos80
 Аватар для moskitos80
39 / 39 / 0
Регистрация: 04.10.2011
Сообщений: 128
20.08.2012, 22:24  [ТС]     Запись и чтение объектов разных классов в один файл #5
Цитата Сообщение от silent_1991 Посмотреть сообщение
Попробуйте этот код откомпилировать в Release-режиме (если это уже не было сделано) и проверьте всё снова.
Удачного отпуска
Ещё раз спасибо. Теперь хочется учить язык дальше
insolent
 Аватар для insolent
826 / 347 / 15
Регистрация: 30.01.2009
Сообщений: 1,204
20.08.2012, 23:14     Запись и чтение объектов разных классов в один файл #6
silent_1991, можно ли в данную проблему решить реализовав конструктор копирования и переопределив оператор присваивания, а чтение производить во временный объект, затем копировать в массив или вектор?
alsav22
5282 / 4801 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
20.08.2012, 23:20     Запись и чтение объектов разных классов в один файл #7
У меня, при повторном запуске, в дебаг такая ошибка (1 скрин), в релиз такая (2 скрин).
Миниатюры
Запись и чтение объектов разных классов в один файл   Запись и чтение объектов разных классов в один файл  
alsav22
5282 / 4801 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
21.08.2012, 00:18     Запись и чтение объектов разных классов в один файл #8
Это в MSVC. В Code Blocks, ошибка, только если дебаг. При релиз - нет.
Миниатюры
Запись и чтение объектов разных классов в один файл  
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
21.08.2012, 02:08     Запись и чтение объектов разных классов в один файл #9
insolent, не понял, о чём речь. Как, вы предполагаете, это решит проблему (если она в том, что я описал)? Временный объект по сути своей в памяти ничем от постоянного не отличается, у него также есть указатель на таблицу виртуальных функций; массив или вектор вообще не понятно, к чему здесь, в них лежат такие же объекты, как и просто отдельный объект, опять же, они имеют скрытые указатели на vtable.
alsav22
5282 / 4801 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
21.08.2012, 12:26     Запись и чтение объектов разных классов в один файл #10
Так можно:
Код
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <iostream>
#include <fstream>
#include <typeinfo>
using namespace std;
 
const int MAX_PERS = 4;
const char* PERS_FILE_NAME = "PERS_DATA.ART";
 
// Типы данных объектов:
enum Ctypes 
{
    Tstudent,
    Temployee
};
 
/////////////////////////////////////////////////////////////////////////////////
class Person
{
    public :
        Person() 
        {
            this->age = 500;
        }
        virtual void getData(void)
        {
            cout  << endl << "Age  " << this->age  << endl;
        }
    protected :
        int age;
};
/////////////////////////////////////////////////////////////////////////////////
class Student : public Person
{
    public :
        Student() : Person(), gpa (13.6f), course (8) 
        {
        }
 
        void getData(void)
        {
            cout << endl << "---------- Student data: ----------" << endl;
            Person::getData();
            cout << endl << "GPA "     << this->gpa
                 << endl << "Course  " << this->course  << endl;
        }
    protected :
        float gpa;
        int course;
};
/////////////////////////////////////////////////////////////////////////////////
class Employee : public Person
{
    public :
        Employee() : Person(), post (555) 
        {
        }
 
        void getData(void)
        {
            cout << endl << "---------- Employee data: ----------" << endl;
            Person::getData();
            cout << endl << "Post "     << this->post << endl;
        }
    protected :
        int post;
};
/////////////////////////////////////////////////////////////////////////////////
class Storage
{
    public :
        bool store(Person** pers1, int cnt)
        {
            Ctypes type;
            int size = 0;
            int n = 0; // количество объектов, в файле.
            
            fstream f(PERS_FILE_NAME, ios::in | ios::out | ios::binary | ios::ate);
            
            if (!f.is_open()) // если файл не существует.
            {
                f.clear();
                ofstream outf(PERS_FILE_NAME, ios::out | ios::binary);
                outf.write((const char*)&n, sizeof(n)); // 0 объектов в файле.
                outf.close();
                f.open(PERS_FILE_NAME, ios::in | ios::out | ios::binary | ios::ate);
            }
            // запись в конец файла.
            for(int i = 0; i < cnt; i++) 
            {
 
                type = this->getType(pers1[i]);
              
                f.write((char*)&type, sizeof(type)); // записываем тип объекта.
                if (!f) 
                {
                    cerr << endl << "Err write type!"; 
                    return false;
                }
                
                if (type == Tstudent) 
                {
                    size = sizeof(Student);
                } 
                else 
                {
                    size = sizeof(Employee);
                }
 
                f.write((char*)pers1[i], size); // записываем объект.
                if (!f) 
                {
                    cerr << endl << "Err write type!"; 
                    return false;
                }
            } //for
            
            f.close();
            
            // открываем файл для перезаписи количества объектов в файле.
            f.open(PERS_FILE_NAME, ios::in | ios::out | ios::binary);
            f.read((char*)&n, sizeof(n)); // считываем старое значение.
            n = n + cnt; // новое количество объектов в файле.
            f.seekp(0); // курсор в начало.
            f.write((const char*)&n, sizeof(n)); // записываем новое значение.      
            
            return true;
        }
 
     //....................................................................
        bool readAll()
        {
            ifstream inf(PERS_FILE_NAME, ios::in | ios::binary);
            if (!inf) 
            {
                cerr << endl << "Err open!"; 
                return false;
            }
            
            int n = 0; // количество объектов  в файле.
            
            inf.read((char*)&n, sizeof(n)); // считываем количество объектов.
            if (!inf) 
            {
                cerr << endl << "Err read type!"; 
                return false;
            }
            
            Person **pers1 = new Person*[n];
            
            int size = 0, cnt = 0;
            Ctypes type;
 
            while (cnt < n) 
            {
                inf.read((char*)&type, sizeof(type)); // считываем тип объекта.
                if (!inf) 
                {
                    cerr << endl << "Err read type!"; 
                    return false;
                }
                
                if (type == Tstudent) 
                {
                    // Инициализация
                    pers1[cnt] = new Student; 
                    size = sizeof(Student);
                } 
                else 
                {
                    // Инициализация
                    pers1[cnt] = new Employee;
                    size = sizeof(Employee);
                }  
                
            //*...выводим на консоль, то что в памяти.  
                    cout << "\nIn memory      ";
                    for (int i = 0; i < size; i++)
                    {
                        cout << hex;
                        cout << (int)(*(((unsigned char*)(pers1[cnt])) + i));
                        cout << " ";
                    }
                    cout << endl;
            //..........................................*/ 
               
               // Считываем из файла.
                inf.ignore(sizeof(Person) / 2); // пропускаем таблицу виртуальных функций.
                
                pers1[cnt] = (Person*)(((char*)(pers1[cnt])) + (sizeof(Person) / 2)); // перемещаем указатель на величину таблицы.
                
                inf.read((char*)pers1[cnt], size - (sizeof(Person) / 2)); // читаем из файла объект без таблицы.
                if (!inf) 
                {
                    cerr << endl << "Err read object!"; 
                    return false;
                }
                
                pers1[cnt] = (Person*)(((char*)(pers1[cnt])) - (sizeof(Person) / 2)); // возвращаем указатель на начало объекта в памяти.
                
            //* выводим на консоль, то что в памяти после считывания из файла. 
                cout << "After reading  ";
                for (int i = 0; i < size; i++)
                {
                    cout << hex;
                    cout << (int)(*(((unsigned char*)(pers1[cnt])) + i));
                    cout << " ";
                }   
                cout << endl;
            //....................................................*/
                cout << dec;
                pers1[cnt] -> getData();
                cnt++;
                }
            
            return true;
        }
//......................................................................
        Ctypes getType(Person* person)
        {
            if (typeid(*person) == typeid(Student)) 
            {
                return Tstudent;
            }
            return Temployee;
        }
};
/////////////////////////////////////////////////////////////////////////////////
 
int main()
{
    Storage storage;
    Person* pers[MAX_PERS];
    
     //Инициализируем массив
    for (int i = 0; i < MAX_PERS; i++) 
    {
        pers[i] = NULL;
    }
    
    // Тупо ручками:
    pers[0] = new Student;
    pers[1] = new Student;
    pers[2] = new Employee;
    pers[3] = new Employee;
    
    // Пишем
    if (storage.store(pers, MAX_PERS)) 
    {
        cout << endl << "STORED OK" << endl;
    }
    
    //Читаем
    storage.readAll();
 
    system("pause");
    return 0;
}
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
22.08.2012, 13:23     Запись и чтение объектов разных классов в один файл #11
alsav22, плохой вариант. Свитч типов + компиляторозависимый пропуск указателя на таблицу виртуальных функций (она может лежать не в начале, её может вообще не быть в случае какой-ниубдь хитроумной реализации динамического полиморфизма и т.д.).
alsav22
5282 / 4801 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
22.08.2012, 23:00     Запись и чтение объектов разных классов в один файл #12
Цитата Сообщение от silent_1991 Посмотреть сообщение
alsav22, плохой вариант. Свитч типов + компиляторозависимый пропуск указателя на таблицу виртуальных функций (она может лежать не в начале, её может вообще не быть в случае какой-ниубдь хитроумной реализации динамического полиморфизма и т.д.).
Согласен насчёт компиляторозависимости. Проверял в MSVC и в mingv. В mingv рзмер таблицы явно другой, и, по моему, ещё какое-то выравнивание происходит (по выводу значений из памяти видно). Но, как ни странно, и там работает. А насчёт
Свитч типов
, если не трудно, для начинающего пояснить.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
23.08.2012, 18:31     Запись и чтение объектов разных классов в один файл
Еще ссылки по теме:

C++ Создание коллекции объектов разных классов
Вектор объектов разных классов. Доступ к свойствам элемента вектора C++
C++ Сериализация объектов с полями std::string + запись/чтение в файл

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
23.08.2012, 18:31     Запись и чтение объектов разных классов в один файл #13
Цитата Сообщение от alsav22 Посмотреть сообщение
если не трудно, для начинающего пояснить
C++
1
2
3
4
5
6
7
8
if (type == Tstudent) 
{
    size = sizeof(Student);
} 
else 
{
    size = sizeof(Employee);
}
C++
1
2
3
4
5
6
7
8
9
10
11
12
if (type == Tstudent) 
{
    // Инициализация
    pers1[cnt] = new Student; 
    size = sizeof(Student);
} 
else 
{
    // Инициализация
    pers1[cnt] = new Employee;
    size = sizeof(Employee);
}
Явный выбор действия в зависимости от типа времени компиляции. Добавить в иерархию новый тип значит добавить как минимум в два места else if для соответствующего типа. Тогда как полиморфизм (то, что предлагал я - каждый конкретный класс умеет читать себя из потока и писать в поток) делает это прозрачно, одной строкой кода на стадии выполнения. Свитч типов - пережиток не-ОО-языков, в ООП на смену емё пришёл динамический полиморфизм.
Yandex
Объявления
23.08.2012, 18:31     Запись и чтение объектов разных классов в один файл
Ответ Создать тему
Опции темы

Текущее время: 20:11. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru