【问题标题】:Serialize struct with a pointer to a struc使用指向结构的指针序列化结构
【发布时间】:2020-09-12 12:29:21
【问题描述】:

我正在尝试关注https://accu.org/index.php/journals/2317 上写的一篇文章 现在我发现这很有趣,因为我正在尝试深入研究我正在开发的一些管理工具的序列化/文件保存/加载。我将它标记为 c++,因为在我让它工作后我会将它包装在一个类中(希望在你的帮助下)。

现在,这是出于学习目的,因为我目前不打算使用 boost 或任何其他序列化库。我想知道或尝试找出这些方法是否有效。

目前,为了工作,我不得不做一些细微的修改,因为断言总是错误的,并且在尝试使用 memcpy 写入外部分配的内存时遇到问题。

现在,它可以编译,但是当我对结构进行序列化和反序列化时,数据不一样。

请查看并帮助我,或指出正确的方向。在代码之后,我将尝试解释我是如何理解它的。

#include <iostream>
#include <cassert>

struct Y
{
    int yy;
};

struct X
{
    int xx;
    struct Y* y = nullptr;
    int z;
};

// Changed OutMemStram and InMemStream for IOMemStream, same data
struct IOMemStream
{
    // changed from uint8_t* to char*
    char* pp;
    char* ppEnd;
};

// Output
inline void WriteToStream( IOMemStream* dst, void* p, size_t sz )
{
    dst->pp = (char*)p; // original code doesn't contain this line
    dst->ppEnd = (char*)p + sz; // original code doesn't contain this line

    assert( (dst->pp + sz) <= dst->ppEnd );
    memcpy( dst->pp, p, sz );
    dst->pp += sz;
}

void SerializeX( IOMemStream* dst, X* x )
{
    WriteToStream( dst, x, sizeof( X ) );
    WriteToStream( dst, x->y, sizeof( Y ) );
}

// Input
inline void ReadFromStream( IOMemStream* src, void* p, size_t sz ) 
{
    //assert( (src->pp + sz) <= src->ppEnd );

    memcpy( p, src->pp, sz );
    src->pp += sz;
}

void DeserializeX( IOMemStream* src, X* x ) 
{
    ReadFromStream( src, x, sizeof( X ) );
    // x->y contains garbage at this point(!)
    // ok, not exactly garbage - but a pointer
     // which is utterly invalid in our current space
    x->y = new Y;
    assert( x->y );
    ReadFromStream( src, x->y, sizeof( Y ) );
}


// Usage sample
int main()
{
    // Assume struct x was previously filled by other function
    X x;
    x.xx = 1000;
    x.z = 2000;
    x.y = new Y;
    x.y->yy = 3000;

    // IO buffer
    IOMemStream ioms;

    // Test for output
    SerializeX( &ioms, &x );

    // Test for input
    X x1;
    DeserializeX( &ioms, &x1 );

    // x1.xx should be 1000 and x1.< should be 2000
    std::cout << x1.xx << ", " << x1.z << std::endl;

    delete x.y;
    delete x1.y;
    //delete ioms.pp; // gets exception

    std::cin.get();
    return 0;
}

这是我理解(或不理解)的方式。

  1. 结构 X,包含 2 个整数和 1 个指向 Y 结构的结构指针,假设整数大小 = 4,则 X 的 大小将为 12 个字节。是的,它是 4 个字节。
  2. IOMemStream 包含结构 X 和结构 Y 的指针。
  3. IOMemStream->pp 和 IOMemStream->ppEnd 应该有 12 个字节和 4 个字节来自 struct X 和 X->Y。
  4. 函数 WriteToStream 将结构中的字节打包到 pp 和ppEnd指针,X赋值后,指针递增 按大小为 Y 结构做好准备。 (在原始文章中, 断言是在没有分配变量 pp 和 ppEnd 的情况下进行的
  5. IOMemStream) 函数 SerializeX 对 X 和 Y 使用 WriteToStrea 结构。

反序列化几乎相同,但顺序相反,ReadFromStream 将为结构 Y 分配内存。

我从这里迷路了,因为序列化和反序列化的值不一样。另外,我希望我能正确理解:D

提前谢谢你!

【问题讨论】:

  • 你是在 valgrind 下运行这段代码,还是用地址清理器编译?
  • 嗨!不,抱歉。需要调查 valgrind 或地址消毒剂是什么。我真的是一个新手,我在网上完成了一些 tuts,我正在尝试编写自己的工具并尝试将其他 ppl 的代码理解为 excercese。
  • //assert( (src-&gt;pp + sz) &lt;= src-&gt;ppEnd ); 行被注释掉是否有特殊原因?这行代码在运行时可能会导致某种问题吗?
  • 是的,我评论它是因为断言总是错误的,即使它使用在 WriteToStream 上传递的同一个变量 (IOMemStream),其中断言是正确的。 (ioms.pp
  • 也许你应该考虑一下为什么断言是错误的?也许,而且我知道这有很多问题要问,您应该问问自己,该断言是否可能可能出于某种原因?也许,不要误会,您可能忽略了与该断言相关的某些内容?

标签: c++ c serialization


【解决方案1】:

免责声明:这只是一堆太长的cmets,不能写成cmets,而不是正确的答案。


事情没那么简单,你有一些错误的假设。特别是,即使 int 占用 4 个字节而指针占用 8 个字节,由于填充,您的 X 结构也可以有超过 16 个字节。

尝试将下面的代码添加到您的 main 中,看看会得到什么

    X x;
    std::cout << sizeof(x.xx) << std::endl;
    std::cout << sizeof(x.y) << std::endl;
    std::cout << sizeof(x.z) << std::endl;
    std::cout << sizeof(x) << std::endl;

在我的系统中,我为每个整数获得 4 个字节,为指针获得 8 个字节,但对于结构,我获得 24 个字节。原因是由于填充(参见this question for more)。请注意,您声明结构成员的顺序对填充有影响。如果你声明了

struct X {
    struct Y* y = nullptr;
    int xx;
    int z;
};

or

```c++
struct X {
    int xx;
    int z;
    struct Y* y = nullptr;
};

那么结构将只需要 16 个字节(至少在我的系统中)。


另一个问题是序列化指针。显然,存储指针值用处不大。您将需要考虑如何处理结构Y。您必须在反序列化期间创建一个Y 类型的对象,然后将其地址放入反序列化的结构X,但细节可能会根据程序的其余部分而改变,这里我们只有示例代码。


最后,小心memcpy,因为它只会保存字节(可能包括填充)并且不关心字节序。

【讨论】:

  • 这很奇怪。我为每个成员获得 4 个字节,为整个结构获得 12 个字节。此外,使用检查工具和 sizeof(x) 查看调试器,也显示 12 个字节。我不应该担心字节序,因为我会在我自己的机器上使用这个工具。至于程序的其余部分,这是整个程序,我想看看这段代码是否可以修改它并满足我自己的类和数据的需求。可能我错了,但我想知道它是如何工作的,以及使用外部库(如(但不限于)boost),感觉就像在作弊:(
  • 您使用的是 32 位系统吗?这可以解释为什么指针只需要 4 个字节。只需 4 个字节,一个指针就可以寻址高达 4Gb 的内存。在 64 位系统中,指针通常占用 8 个字节。此外,由于在您的情况下指针和整数使用相同数量的内存,因此不需要填充,结构确实只需要 12 个字节。
  • 我正在编译到 x86,(默认选择)根本没有什么大的原因。这看起来像 ISOCPP 的序列化示例(使用指针继承),我也无法弄清楚(还)。但看起来很有趣,它让代码看起来有点“性感”:D
【解决方案2】:

我读了更多的文章,似乎他们在流对象中接收的数据来自机器,这就是为什么 pp 和 ppend,可能在那里保存了“之前和之后”状态。另外,这些功能似乎只是为了这个目的。

太糟糕了,因为我认为这是可以学习的好代码,可能我对它感到沮丧并且错过了理解这个概念。

很抱歉浪费你们的时间了!但我不会放弃的:)

有什么书籍或网站可以让您了解更多有关连载的信息吗?目前我不想使用任何外部库,因为我想了解更多并练习使用自己的工具。出于某种原因,使用外部库对我来说就像在作弊,让我觉得我没有走正确的道路。

谢谢你,问候!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多