SQLite3 Blob读取时boost::binary_iarchive替代text_iarchive异常排查
我对流与缓冲区相关技术不算精通,但着手处理这个问题的一个月里已经积累了大量相关实践经验。
本次问题涉及boost::serialization模块:若能成功启用二进制归档能力,可节省50%的存储空间。我检索了大量相关资料,拼凑出了当前可正常运行的代码,但这套代码仅支持text_iarchive。当我尝试替换为binary_iarchive时,程序会触发段错误,报错信息为boost serialize allocate(size_t n) 'n' exceeds maximum supported size,或是出现其他可明确判定为输入流/缓冲区与binary_iarchive预期数据格式不匹配的错误。
当前代码使用text_iarchive时运行完全正常:可以通过text_oarchive序列化数据写入SQLite3 Blob字段,在数据库中验证数据正确性,读取数据后通过text_iarchive反序列化还原为复杂对象,全流程无异常。
本次需要确认输入流与缓冲区的配置方式是否存在错误。
为避免无关信息干扰,此处不贴出序列化/反序列化目标对象的具体结构:该对象包含多个vector<double>、一个Eigen Matrix以及若干基础对象,这部分逻辑运行完全正常,不属于问题诱因(测试时每次都会删除数据库旧记录,不会出现将text_oarchive生成的归档读入binary_iarchive的格式不匹配情况)。
输出归档段代码
该段代码使用text_oarchive或binary_oarchive时均运行正常,写入数据库的Blob字段结构符合预期:
// BinaryData is a Typedef for std::vector<char> BinaryData serializedDataStream; bio::stream<bio::back_insert_device<BinaryData>> outbuf {serializedDataStream}; // 切换到二进制归档时,原本的操作是将text_oarchive替换为binary_oarchive,并取消std::ios::binary参数的注释 boost::archive::text_oarchive outStream(outbuf); //, std::ios::binary); outStream << ssInputDataAndBestModel_->theModel_; outbuf.flush(); // SQLite3的Blob类型要求传入unsigned char类型数据,此处做类型转换 std::vector<unsigned char> buffer(serializedDataStream.begin(),serializedDataStream.end()); // 后续将buffer传入SQLite处理对象,存入Blob字段
输入归档段代码
无论存储的是文本格式还是二进制格式归档,从数据库读取取出的Blob数据与写入时完全一致(文本格式与二进制格式的Blob内容本身存在差异):
// 从SQLite3中读取Blob字段 currentModelDBRecPtr = cpp17::any_cast<dbo::ptr<Model>>(modelListModel); if (!currentModelDBRecPtr->theModel.empty()) { // boost::serialize要求传入char类型数据,Blob存储的是unsigned char数组,此处做类型转换 std::vector<char> blobBuffer(currentModelDBRecPtr->theModel.begin(), currentModelDBRecPtr->theModel.end()); boost::iostreams::stream<boost::iostreams::array_source> membuf(blobBuffer.data(), blobBuffer.size()); std::istream &input_stream = membuf; // 切换到二进制归档时,原本的操作是将text_iarchive替换为binary_iarchive,并取消std::ios::binary参数的注释 boost::archive::text_iarchive input_archive(input_stream); //, std::ios::binary); TheModel inputArchiveModel; // 执行到下一行时崩溃,崩溃前已经能成功还原一半对象,崩溃位置随机 input_archive >> inputArchiveModel; }
反序列化执行到input_archive >> inputArchiveModel;行时程序崩溃,但崩溃前已经成功还原了一半的对象结构,崩溃位置随机。
你踩了Boost.Serialization和Boost.Iostreams配合时最常见的坑:
- 传给
binary_oarchive/binary_iarchive构造函数的std::ios::binary参数完全不会修改底层流的打开模式,这个参数只是archive自身的配置标记,不影响流的行为,之前修改这个参数的操作没有实际作用。 boost::iostreams::stream默认以文本模式打开设备,在Windows平台下文本模式会自动做换行符转换:写入时把单字节0x0A替换成双字节0x0D 0x0A,读取时反向替换。二进制归档里的0x0A只是普通数值字节,被篡改后整个归档的字节偏移、长度字段会完全错乱,就会出现「allocate size超过最大值」的报错、随机位置崩溃的现象——这和描述的「能还原一半对象再崩溃」完全吻合:前半段数据刚好没出现0x0A字节所以解析正常,读到被篡改的位置就直接解析失败。- 现有代码中
vector<char>和vector<unsigned char>互转逻辑、SQLite Blob读写逻辑都是正确的,不是问题来源。
只需要修改流初始化的代码,显式给Boost.Iostreams的流指定二进制打开模式即可:
- 修改输出段流初始化代码,给输出流加二进制打开标记,移除传给archive的无效
std::ios::binary参数:// 初始化输出流时直接指定二进制打开模式 bio::stream<bio::back_insert_device<BinaryData>> outbuf {serializedDataStream, std::ios::out | std::ios::binary}; // 二进制归档初始化不需要额外传std::ios::binary参数 boost::archive::binary_oarchive outStream(outbuf); - 修改输入段流初始化代码,给输入流加二进制打开标记,移除传给archive的无效
std::ios::binary参数:// 初始化输入流时直接指定二进制打开模式 boost::iostreams::stream<boost::iostreams::array_source> membuf(blobBuffer.data(), blobBuffer.size(), std::ios::in | std::ios::binary); std::istream &input_stream = membuf; // 二进制归档初始化不需要额外传std::ios::binary参数 boost::archive::binary_iarchive input_archive(input_stream); - 注意保持变量的初始化/析构顺序:现有代码是先定义流、再定义archive,离开作用域时archive先析构(会完成剩余归档数据的读写)、流再析构,这个顺序是正确的,不需要调整。手动调用
outbuf.flush()的逻辑可以保留,不会产生副作用。
内容的提问来源于stack exchange,提问作者Mark Travis

