ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

C++: чтение объекта из файла и __vfptr

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

Посмотрим, как в программе empl_io.cpp из учебника Лафоре (стр.574) выполняется запись объекта одного из производных классов базового класса «работник» в файл (тут и далее речь про режим ios::binary):
ouf.write((char*)(arrap[j]), size);
Чтение объекта из файла:
inf.read((char*)arrap[n], size);
Эти операции разнесены в разные методы класса, которые могут быть при желании вызваны пользователем из текстового интерфейса программы.

По-видимому, автор программы empl_io.cpp либо рассчитывал, что читатель учебника будет использовать функции этой программы только в рамках одного сеанса ее работы, либо, если программа рассчитана на работу в рамках нескольких сеансов, значит, в программе допущена ошибка — при чтении объекта из файла не учтено, что указатель __vfptr на таблицу виртуальных методов мог смениться с момента записи объекта в файл (эта ситуация подробно разобрана в предыдущем посте).

Обсуждения на «Киберфоруме»:
17.08.2012 http://www.cyberforum.ru/cpp-beginners/thread638909.html
29.07.2013 http://www.cyberforum.ru/cpp-beginners/thread930619.html
07.10.2013 http://www.cyberforum.ru/cpp-beginners/thread971573.html
На портале «Stack Overflow»:
29.12.2010 https://stackoverflow.com/questions/4550503/what-happens-when-you-use-fwrite-with-a-class
03.07.2017 https://stackoverflow.com/questions/44892894/could-not-call-virtual-member-function-after-read-object-from-file

Как исправить? Вообще, тема сериализации (процесса перевода какой-либо структуры данных в последовательность битов) очень большая, ее нужно изучать отдельно. Я тут рассмотрю простой способ (который может не сработать в других компиляторах или операционных системах по причине различий в организации работы с виртуальными функциями, об этом упоминалось в предыдущем посте), предложенный в обсуждении на «Киберфоруме» — пропускать при чтении объекта байты, относящиеся к полю-указателю __vfptr.

Реализация исправлений. В принципе, если мы при чтении объекта из файла не собираемся читать байты, относящиеся к полю-указателю __vfptr, то зачем их вообще записывать в файл? Логичнее пропускать их уже при записи, да и размер файла уменьшится на 4 байта для каждого записанного объекта (в случае 32-разрядного приложения):
ouf.write((char*)(arrap[j]) + sizeof(void*), size - sizeof(void*));
Тогда чтение объекта из файла должно происходить следующим образом:
inf.read((char*)arrap[n] + sizeof(void*), size - sizeof(void*));

Что тут происходит. sizeof(void*) — размер значения типа void* (указателя), то есть для нашего случая (32-разрядное приложение) это выражение равно 4 байтам. Можно было бы вместо sizeof(void*) везде вставить число 4, но sizeof(void*) универсальнее, так как учитываются случаи, когда размер указателя не равен 4 байтам (например, для 64-разрядного приложения размер указателя равен 8 байтам).

При записи первым параметром должен быть указатель на начало блока данных в памяти, который требуется записать в файл. Прибавляя к этому указателю sizeof(void*), мы сдвигаем указатель начала блока памяти ближе к его концу, отсекая от него начальный кусок, содержащий поле-указатель __vfptr. Ну а если блок памяти уменьшился, то и информацию о его размере следует подкорректировать, что мы и делаем со вторым параметром, вычитая из него sizeof(void*).

При чтении первым параметром должен быть указатель на начало блока данных в памяти, в который требуется записать данные из файла. Это элемент массива arrap[n], который содержит только что созданный пустой объект нужного класса, в котором уже имеется поле-указатель __vfptr с правильным адресом таблицы виртуальных методов. Это поле важно не затереть данными, прочитанными из файла. Поэтому, прибавляя к (char*)arrap[n] выражение sizeof(void*), мы сдвигаем указатель начала блока данных в памяти ближе к его концу, уменьшая его и оставляя поле-указатель __vfptr невредимым. Прочитанные из файла данные запишутся в пустой объект, не затронув поле-указатель __vfptr. Соответственно, корректируем информацию о размере блока памяти, вычитая из этого размера выражение sizeof(void*).

Полный текст программы можно посмотреть тут: empl_io.cpp.
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments