C++ 为什么非侵入式序列化会添加5字节零前缀?

C++ 为什么非侵入式序列化会添加5字节零前缀?,c++,serialization,boost,C++,Serialization,Boost,我正在研究使用boost::archive的应用程序中从非标准到标准字符串的端口。非标准字符串以非侵入式样式定义其(反)序列化,如下例所示。序列化和反序列化按预期工作,但当移植的应用程序收到旧消息时,它会因错误的分配而崩溃。这是因为在字符串大小之前插入了5个字节(全部为零) 是什么导致插入这5个额外字节?这是某种神奇的记号笔吗 例如: #include <iostream> #include <string> #include <sstream> #inclu

我正在研究使用boost::archive的应用程序中从非标准到标准字符串的端口。非标准字符串以非侵入式样式定义其(反)序列化,如下例所示。序列化和反序列化按预期工作,但当移植的应用程序收到旧消息时,它会因错误的分配而崩溃。这是因为在字符串大小之前插入了5个字节(全部为零)

是什么导致插入这5个额外字节?这是某种神奇的记号笔吗

例如:

#include <iostream>
#include <string>
#include <sstream>
#include <boost/serialization/split_free.hpp>
#include <boost/archive/binary_oarchive.hpp>

struct own_string { // simplified custom string class
    std::string content;
};

namespace boost
{
    namespace serialization
    {
        template<class Archive>
        inline void save(
            Archive & ar,
            const own_string & t,
            const unsigned int /* file_version */)
        {
            size_t size = t.content.size();
            ar << size;
            ar.save_binary(&t.content[0], size);
        }

        template<class Archive>
        inline void load(
            Archive & ar,
            own_string & t,
            const unsigned int /* file_version */)
        {
            size_t size;
            ar >> size;
            t.content.resize(size);
            ar.load_binary(&t.content[0], size);
        }

// split non-intrusive serialization function member into separate
// non intrusive save/load member functions
        template<class Archive>
        inline void serialize(
            Archive & ar,
            own_string & t,
            const unsigned int file_version)
        {
            boost::serialization::split_free(ar, t, file_version);
        }

    } // namespace serialization
} // namespace boost

std::string string_to_hex(const std::string& input)
{
    static const char* const lut = "0123456789ABCDEF";
    size_t len = input.length();

    std::string output;
    output.reserve(2 * len);
    for (size_t i = 0; i < len; ++i)
    {
        const unsigned char c = input[i];
        output.push_back(lut[c >> 4]);
        output.push_back(lut[c & 15]);
    }
    return output;
}

void test_normal_string()
{
    std::stringstream ss;
    boost::archive::binary_oarchive ar{ss};

    std::string test = "";

    std::cout << string_to_hex(ss.str()) << std::endl;
    ar << test;

    //adds 00 00 00 00 00 00 00 00
    std::cout << string_to_hex(ss.str()) << std::endl;
}

void test_own_string()
{
    std::stringstream ss;
    boost::archive::binary_oarchive ar{ss};

    std::string test = "";

    own_string otest{test};
    std::cout << string_to_hex(ss.str()) << std::endl;
    ar << otest;

    //adds 00 00 00 00 00 00 00 00 00 00 00 00 00
    std::cout << string_to_hex(ss.str()) << std::endl;
}

int main()
{
    test_normal_string();
    test_own_string();
}
#包括
#包括
#包括
#包括
#包括
struct own_string{//简化的自定义字符串类
std::字符串内容;
};
名称空间提升
{
命名空间序列化
{
模板
内联作废保存(
存档和ar,
const own_string&t,
常量unsigned int/*文件_version*/)
{
size_t size=t.content.size();
ar>大小;
t、 内容。调整大小(大小);
ar.load_二进制文件(&t.content[0],大小);
}
//将非侵入式序列化函数成员拆分为单独的
//非侵入式保存/加载成员函数
模板
内联空序列化(
存档和ar,
自己的字符串和t,
常量无符号整数文件(U版本)
{
boost::serialization::split_free(ar、t、file_版本);
}
}//命名空间序列化
}//名称空间提升
std::string字符串到十六进制(const std::string和input)
{
静态常量字符*常量lut=“0123456789ABCDEF”;
size_t len=input.length();
std::字符串输出;
输出。储备(2*len);
对于(尺寸i=0;i>4]);
输出。推回(lut[c&15]);
}
返回输出;
}
无效测试\u正常\u字符串()
{
std::stringstream-ss;
boost::archive::binary_oarchive ar{ss};
std::string test=“”;

std::cout因此,您希望将先前序列化的
own_字符串
反序列化,就像它是
std::字符串一样

发件人:

默认情况下,对于每个序列化的类,类信息都会写入归档文件。这些信息包括版本号、实现级别和跟踪行为。这是必要的,这样即使程序的后续版本更改了类的某些当前特征值,归档文件也可以正确反序列化。空格此数据的开销最小。由于必须检查每个类是否已将其类信息包含在存档中,因此会有一点运行时开销。在某些情况下,甚至可能会认为这一开销过大。可以通过将实现级别的类特征设置为:boost::serializa来消除此额外开销操作::对象可序列化

现在,可能(*)这是标准类的默认值

BOOST_CLASS_IMPLEMENTATION(own_string, boost::serialization::object_serializable)
在全局范围内,使test_X_字符串结果具有相同的字节。这应该可以解释观察到的额外字节差异

也就是说,我没有找到任何关于标准类序列化特性的具体保证(其他人可能比我更清楚)

(*)实际上提到:

避免此问题的另一种方法是为所有基元类型的模板my_包装的所有专门化指定序列化特征,以便永远不会保存类信息。这就是我们为STL集合实现序列化所做的工作。


因此,这可能会给您足够的信心,让标准集合(因此包括std::string)在这种情况下提供相同的字节。

我认为您要求的是未记录的实现细节。不必解释为什么。这是存档格式的实现细节

这是一个有趣的问题

您必须告诉库您不需要类型的所有功能(对象跟踪、类型信息、版本控制)。具体来说,这演示了如何实现相同的封装

请注意,您显然会丢失禁用的功能

#include <iostream>
#include <string>
#include <sstream>

struct own_string { // simplified custom string class
    std::string content;
};

#include <boost/serialization/split_free.hpp>
#include <boost/serialization/tracking.hpp>
BOOST_CLASS_IMPLEMENTATION(own_string, boost::serialization::level_type::object_serializable)
BOOST_CLASS_TRACKING(own_string, boost::serialization::track_never)

//#include <boost/serialization/wrapper.hpp>
//BOOST_CLASS_IS_WRAPPER(own_string)

#include <boost/serialization/array_wrapper.hpp>

namespace boost
{
    namespace serialization
    {
        template<class Archive>
        inline void save(
            Archive & ar,
            const own_string & t,
            const unsigned int /* file_version */)
        {
            size_t size = t.content.size();
            ar & size;
            if (size)
                ar & boost::serialization::make_array(&t.content[0], size);
        }

        template<class Archive>
        inline void load(
            Archive & ar,
            own_string & t,
            const unsigned int /* file_version */)
        {
            size_t size;
            ar & size;
            t.content.resize(size);
            if (size)
                ar & boost::serialization::make_array(&t.content[0], size);
        }

        // split non-intrusive serialization function member into separate
        // non intrusive save/load member functions
        template<class Archive>
        inline void serialize(
            Archive & ar,
            own_string & t,
            const unsigned int file_version)
        {
            boost::serialization::split_free(ar, t, file_version);
        }

    } // namespace serialization
} // namespace boost

std::string string_to_hex(const std::string& input)
{
    static const char* const lut = "0123456789ABCDEF";
    size_t len = input.length();

    std::string output;
    output.reserve(2 * len);
    for (size_t i = 0; i < len; ++i)
    {
        const unsigned char c = input[i];
        output.push_back(lut[c >> 4]);
        output.push_back(lut[c & 15]);
    }
    return output;
}

#include <boost/archive/binary_oarchive.hpp>

void test_normal_string()
{
    std::stringstream ss;
    {
        boost::archive::binary_oarchive ar{ss, boost::archive::no_header|boost::archive::no_codecvt};

        std::string test = "";

        //std::cout << string_to_hex(ss.str()) << std::endl;
        ar << test;
    }

    //adds 00 00 00 00 00 00 00 00
    std::string bytes = ss.str();
    std::cout << string_to_hex(bytes) << " (" << bytes.size() << " bytes)\n";
}

void test_own_string()
{
    std::stringstream ss;
    {
        boost::archive::binary_oarchive ar{ss, boost::archive::no_header|boost::archive::no_codecvt};

        own_string otest{""};
        //std::cout << string_to_hex(ss.str()) << std::endl;
        ar << otest;
    }

    //adds 00 00 00 00 00 00 00 00 00 00 00 00 00
    std::string bytes = ss.str();
    std::cout << string_to_hex(bytes) << " (" << bytes.size() << " bytes)\n";
}

int main()
{
    test_normal_string();
    test_own_string();
}

请注意,该示例消除了许多其他干扰源/开销。

什么是“旧消息”?关于额外的字节,我想这只是版本控制数据…旧消息是使用自定义字符串的消息。@choeger只需修复问题措辞即可消除混淆!+1对于优秀的自包含示例,大小可以不同在编译器、编译器版本和平台之间。序列化非固定大小的类型往往会破坏大多数序列化协议。@Yakk但不是这样。跟踪这一点确实很有趣
0000000000000000 (8 bytes)
0000000000000000 (8 bytes)