首页 > 解决方案 > 使用指向结构的指针序列化结构

问题描述

我正在尝试关注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 结构的结构指针,假设 int size = 4,那么 X 的大小将为 12 个字节。Y 它是 4 个字节。
  2. IOMemStream 包含 struct X 和 struct Y 的指针。
  3. IOMemStream->pp 和 IOMemStream->ppEnd 应该有 12 个字节和来自 struct X 和 X->Y 的 4 个字节。
  4. 函数 WriteToStream 将结构中的字节打包到 pp 和 ppEnd 指针,在分配 X 后,指针增加大小以准备 Y 结构。(在原始文章中,断言是在没有分配变量 pp 和 ppEnd 的情况下进行的
  5. IOMemStream) 函数 SerializeX 对 X 和 Y 结构都使用 WriteToStrea。

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

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

先感谢您!

标签: c++cserialization

解决方案


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


事情不是那么简单,你有一些错误的假设。特别是,即使 anint占用 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 个字节。原因是由于填充(有关更多信息,请参阅此问题)。请注意,您声明结构成员的顺序对填充有影响。相反,如果您声明

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

or

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

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


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


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


推荐阅读