嗨,我目前正在开发一个模拟程序,该程序尝试在请求时将程序的状态(变量和对象)保存到二进制文件中,以便在需要时可以恢复模拟。

请注意:我知道这在不同的 CPU 架构之间不兼容,这绝对没问题!

一切似乎都运行良好,直到将具有虚拟方法的对象写入文件,然后尝试将其读回。

下面的代码说明了这个问题:

header.hpp

using namespace std;

class parent
{
        public:
                int mValue;
                virtual string getName() =0;
                virtual size_t getSize() =0;

                parent(int value) : mValue(value)
                {

                }
};

class bob : public parent
{
        public:
                bob(int value) : parent(value)
                {

                }

        string getName();
        size_t getSize() { return sizeof(bob); }
};

string bob::getName()
{
        string name("bob");
        return name;
}

class sarah : public parent
{
        public:
                sarah(int value) : parent(value)
                {

                }

        string getName();
        size_t getSize() { return sizeof(sarah); }
};

string sarah::getName()
{
        string name("sarah");
        return name;
}

write.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "header.hpp"

int main()
{
    sarah girl(1);
    bob boy(2);

    parent* child1 = &girl;
    parent* child2 = &boy;

    cout << "Created child called " << child1->getName() << endl;
    cout << "Created child called " << child2->getName() << endl;

    //save sarah and bob to a binary file
    ofstream file("temp.bin", ios::binary | ios::trunc);

    if(!file.is_open())
            return 1;

    //format <size><data><size><data>....
    size_t tempSize=0;

    //write child1
    tempSize = child1->getSize();
    file.write( (char*) &tempSize,sizeof(size_t));

    file.write( (char*) child1,tempSize);

    tempSize = child2->getSize();
    file.write( (char*) &tempSize,sizeof(size_t));

    file.write( (char*) child2,tempSize);

    file.close();

    return 0;
}

read.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include "header.hpp"

int main()
{

    //read sarah and bob from a binary file
    ifstream file("temp.bin", ios::binary);

    //format <size><data><size><data>....
    size_t tempSize=0;

    //get size of child1
    file.read( (char*) &tempSize, sizeof(size_t));

    //allocate memory for child1
    parent* child1= (parent*) malloc(tempSize);
    //read child 1 back
    file.read( (char*) child1,tempSize);

    //get size of child2
    file.read( (char*) &tempSize, sizeof(size_t));

    //allocate memory for child2
    parent* child2= (parent*) malloc(tempSize);
    //read child 2 back
    file.read( (char*) child2,tempSize);

    file.close();

    //Using virtual methods causes SEGFAULT
    cout << "Recreated child" << child1->getName() << endl;
    cout << "Recreated child" << child2->getName() << endl;



    return 0;
}

并按如下方式构建和运行:
g++ -g write.cpp -o write ; ./write
g++ -g read.cpp -o read  ; ./read

当我逐步执行 gdb 中的读取程序时,我注意到问题似乎出在 v-table 指针上。当我在读取程序中重新创建“sarah”(child1)时,v-table 指针是存在于写入程序中的指针,而不是读取程序。所以大概这个写程序中“sarah”的v-table指针指向一个无效的内存区域,这导致了SEGFAULT。

我有两个问题:
  • 是否可以在“写入”程序中将v-table指针信息保存到二进制文件中,这样我的对象就可以在“正确”的程序中完美重新创建,而无需求助于Boost::Serialization或POST++等库来处理这是给我的?
  • 如果这是不可能的......或者如果它非常复杂,那么我将不得不添加一个构造函数和一个“saveState()”方法(可以分别作用于 ifstream 和 ofstream 对象),以便每个类(在这个case sarah 和 bob) 处理从二进制文件中保存和读取它的状态。问题在于我有多个派生自“父”类的类,因此我需要一种方法让“读取”程序通过读取二进制文件来确定要调用哪个构造函数。

    我想出了一种确定要调用哪个构造函数的方法。这将是
  • 给每个派生自“父”的类一个唯一的 ID
  • 在“write”程序中为二进制文件添加唯一ID
  • 在“read”程序中读取每个唯一的ID,然后使用switch语句调用相关的构造函数。

    这不是很优雅,因为每次我添加一个派生自“父”的新类时,我都必须给它一个 ID 并将它添加到“读取”中的 switch 语句中。有更好的方法吗?

  • 感谢阅读,我知道我的帖子很长!

    最佳答案

    每次编译程序时,它都会将函数放在内存中的不同位置。此外,在某些操作系统配置中,每次重新启动程序时,函数甚至可能会移动。这是一种称为地址空间布局随机化的安全功能。如果您确定将从完全相同的二进制文件中读取和写入对象,那么您可以通过将读取和写入函数放在同一个程序中而不是两个不同的程序中来做您想做的事情。然而,即使这样也充满了问题,如果您进行更改并重新编译,您将无法再读取旧数据文件。
    Boost::Serialization 是专门为避免所有这些问题而创建的,包括我确定一些我什至不知道的问题,经过严格的同行评审和测试,并且有一个非常自由的许可证作为奖励。使用这样的图书馆不是“诉诸”的事情,而是一种特权。

    10-07 19:52
    查看更多