【问题标题】:Serializing C-style structs (using C++)序列化 C 风格的结构(使用 C++)
【发布时间】:2011-04-25 16:42:54
【问题描述】:

使用 memcpy 序列化结构对象是邪恶的吗?

在我的一个项目中,我正在执行以下操作:我 memcpy 一个结构对象,base64 对其进行编码,并将其写入文件。我在解析数据时做相反的事情。它似乎可以正常工作,但在某些情况下(例如,当使用WINDOWPLACEMENT 作为 Windows Media Player 的 HWND 时),解码后的数据与sizeof(WINDOWPLACEMENT) 不匹配。

以下是一些代码片段:

// Using WINDOWPLACEMENT from Windows API headers:
typedef struct tagWINDOWPLACEMENT {
    UINT  length;
    UINT  flags;
    UINT  showCmd;
    POINT ptMinPosition;
    POINT ptMaxPosition;
    RECT  rcNormalPosition;
#ifdef _MAC
    RECT  rcDevice;
#endif
} WINDOWPLACEMENT;


static std::string EncodeWindowPlacement(const WINDOWPLACEMENT & inWindowPlacement)
{
    std::stringstream ss;
    {
        Poco::Base64Encoder encoder(ss); // From the Poco C++ libraries
        const char * offset = reinterpret_cast<const char*>(&inWindowPlacement);
        std::vector<char> buffer(offset, offset + sizeof(inWindowPlacement));
        for (size_t idx = 0; idx != buffer.size(); ++idx)
        {
            encoder << buffer[idx];
        }
        encoder.close();
    }
    return ss.str();
}


static WINDOWPLACEMENT DecodeWindowPlacement(const std::string & inEncoded)
{
    std::string decodedString;
    {
        std::istringstream istr(inEncoded);
        Poco::Base64Decoder decoder(istr); // From the Poco C++ libraries
        decoder >> decodedString;
        assert(decoder.eof());
        if (decoder.fail())
        {
            throw std::runtime_error("Failed to parse Window placement data from the configuration file.");
        }
    }

    if (decodedString.size() != sizeof(WINDOWPLACEMENT))
    {
        // !! Occurs frequently !!
        throw std::runtime_error("Errors occured during parsing of the Window placement.");
    }

    WINDOWPLACEMENT windowPlacement;
    memcpy(&windowPlacement, &decodedString[0], decodedString.size());
    return windowPlacement;
}

我知道在 C++ 中使用 memcpy 复制类可能会导致麻烦,因为复制构造函数没有正确执行。我不确定这是否也适用于 C 风格的结构。或者内存转储的序列化只是没有完成?

更新: Poco 的 Base64Encoder/Decoder 中的错误并非不可能,但不太可能。它的测试用例看起来相当彻底:Base64Test.cpp。

【问题讨论】:

  • memcpy 对类造成问题的可能性与对结构的影响相同。除非您考虑到大多数人将结构用作 POD 类型,而将类用作更复杂类型的事实。 C++ 中的结构也使用复制构造函数。并且默认的复制构造函数相当于一个 memcpy,至少对于 POD 类型来说是这样。
  • 关于 Hans 观点,在结构定义中告诉编译器明确的“打包”指令并使用固定大小的字段是明智的。如果您不能这样做,那么另一种选择是将大小字段作为结构的第一个成员。大小字段可以用作版本,可以在加载时进行比较,如果不匹配,将来的版本可以进行调整。

标签: c++ c serialization


【解决方案1】:

如果您需要在不共享相同字节顺序和字长的机器之间传输这些文件,或者如果您在未来版本中从结构中添加/删除插槽并需要保持二进制兼容性,您将遇到问题。

【讨论】:

    【解决方案2】:

    我不确定operator&gt;&gt;() 是如何在Poco::Base64Decoder 中实现的。如果它与istream 的operator&gt;&gt;() 相同,那么在decoder &gt;&gt; decodedString; 之后decodedString 可能不包含输入中的所有字符。例如,如果编码字符串中有任何空格字符,则decoder &gt;&gt; decodedString; 将读取到该空格。

    【讨论】:

      【解决方案3】:

      如果它们只是Plain Old Data (POD),则对类/结构进行memcpy 是可以的,但如果是这种情况,那么您可以依靠C++ 通过复制构造函数为您进行复制(@987654326 都存在) @ 和 class 类型在 C++ 中)。

      当然,您可以按照您一直以来的方式进行操作 - 我使用memcpy 序列化数据的产品之一,通过网络发送数据,客户端应用程序解码字节流以取回数据.

      但如果您有选择,您可能想要更高级别的东西,例如 boost.serialization,它提供更大的灵活性和深指针复制。前面提到的 Google ProtoBuffers 也可以很好地工作。

      这里有一些讨论 C++ 中的序列化方法的线程:

      【讨论】:

        【解决方案4】:

        我不会说它是邪恶的,但我认为它在很多情况下是在自找麻烦和奇怪的问题。

        我知道它已经完成并且可以工作(我见过人们序列化这样的结构以通过网络连接发送),但它有一些已经指出的缺点(不灵活、字节顺序问题、包含指针、打包等的结构体)。

        我会推荐一种更强大的方法来序列化和反序列化您的数据。我听说过很多关于Google protocol buffers 的好消息,这样的事情会更加灵活,最终可能会让您免于头疼。

        【讨论】:

          【解决方案5】:

          如果您知道自己停留在具有相同字节大小、字长、字节序等的机器上,那么以您已经完成的方式序列化数据并不是特别邪恶。因为您正在序列化窗口放置信息,您可能不关心两台不同机器之间的可移植性,而只想在同一台机器上的会话之间保存此信息。我冒昧地猜测您正在将其存储到注册表中。如果您希望移植到其他架构时实际有用的其他数据的可移植性,那么您可以查看这里已经发布的许多其他建议,例如 Google 协议缓冲区等。空白是一个红鲱鱼,因为所有 WS在 base64 编码的数据流中无关紧要,所有解码器都应该忽略它(PoCo 会)。我很想知道字符串的大小和失败时的结构。知道这一点可能会让您对问题有所了解。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-05-02
            • 1970-01-01
            • 2015-01-19
            相关资源
            最近更新 更多