Bharat Bharat - 1 month ago 13
C++ Question

Boost Serialization memory leak with pointer to derived class

I'm using boost serialization to serialize and deserialize a derived type inside a shared_ptr. The derived type dynamically allocated memory because it contains an stl::vector of size 25. Here is the simplest test case:

#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/export.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

using namespace std;

class Params {
public:
virtual void print() = 0;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int version) {}
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Params)

class LogParams : public Params {
private:
//double *data;
std::vector<double> data;

public:
LogParams() { data = std::vector<double>(25, 1.5); }

virtual void print() { cout << data[0] << endl; }

friend class boost::serialization::access;
BOOST_SERIALIZATION_SPLIT_MEMBER()
template<class Archive>
void save(Archive& ar, const unsigned int version) const {
ar & boost::serialization::base_object<Params>(*this);
ar & data;
}
template <class Archive>
void load(Archive& ar, const unsigned int version) {
ar & boost::serialization::base_object<Params>(*this);
ar & data;
}
};
BOOST_CLASS_EXPORT(LogParams)

int main(int argc, char** argv) {
// create Params
shared_ptr<Params> params;
params = make_shared<LogParams>();
shared_ptr<Params> deserializedParams;

for (int i = 0; i < 1000000; ++i) {
// serialize params
string data_string;
ostringstream oarchive_stream;
boost::archive::binary_oarchive oarchive(oarchive_stream);
oarchive << params;
data_string = oarchive_stream.str();

// deserialize params
istringstream iarchive_stream(data_string);
boost::archive::binary_iarchive iarchive(iarchive_stream);
iarchive >> deserializedParams;
deserializedParams->print();
}
}


To build this code use:

g++ -O0 -g -std=c++11 main.cpp -o app -lboost_serialization


When running the program, I can see in top/htop that the amount of memory used (RES) continually increases. Also, if I change the loop to just run once and using valgrind, I get the following output:

valgrind --tool=memcheck --leak-check=full ./app
==7534== Memcheck, a memory error detector
==7534== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7534== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==7534== Command: ./app
==7534==
1.5
==7534==
==7534== HEAP SUMMARY:
==7534== in use at exit: 200 bytes in 1 blocks
==7534== total heap usage: 55 allocs, 54 frees, 5,684 bytes allocated
==7534==
==7534== 200 bytes in 1 blocks are definitely lost in loss record 1 of 1
==7534== at 0x4C29180: operator new(unsigned long) (vg_replace_malloc.c:324)
==7534== by 0x407349: __gnu_cxx::new_allocator<double>::allocate(unsigned long, void const*) (new_allocator.h:104)
==7534== by 0x406FAB: std::allocator_traits<std::allocator<double> >::allocate(std::allocator<double>&, unsigned long) (alloc_traits.h:357)
==7534== by 0x406BE1: std::_Vector_base<double, std::allocator<double> >::_M_allocate(unsigned long) (stl_vector.h:170)
==7534== by 0x4067B4: std::_Vector_base<double, std::allocator<double> >::_M_create_storage(unsigned long) (stl_vector.h:185)
==7534== by 0x40626E: std::_Vector_base<double, std::allocator<double> >::_Vector_base(unsigned long, std::allocator<double> const&) (stl_vector.h:136)
==7534== by 0x405DF3: std::vector<double, std::allocator<double> >::vector(unsigned long, double const&, std::allocator<double> const&) (stl_vector.h:291)
==7534== by 0x405A4C: LogParams::LogParams() (in /home/w4118/dev/serializationTest/app)
==7534== by 0x407E6D: void boost::serialization::access::construct<LogParams>(LogParams*) (access.hpp:130)
==7534== by 0x4079C6: void boost::serialization::load_construct_data<boost::archive::binary_iarchive, LogParams>(boost::archive::binary_iarchive&, LogParams*, unsigned int) (serialization.hpp:92)
==7534== by 0x4074B7: void boost::serialization::load_construct_data_adl<boost::archive::binary_iarchive, LogParams>(boost::archive::binary_iarchive&, LogParams*, unsigned int) (serialization.hpp:148)
==7534== by 0x407036: boost::archive::detail::pointer_iserializer<boost::archive::binary_iarchive, LogParams>::load_object_ptr(boost::archive::detail::basic_iarchive&, void*, unsigned int) const (iserializer.hpp:351)
==7534==
==7534== LEAK SUMMARY:
==7534== definitely lost: 200 bytes in 1 blocks
==7534== indirectly lost: 0 bytes in 0 blocks
==7534== possibly lost: 0 bytes in 0 blocks
==7534== still reachable: 0 bytes in 0 blocks
==7534== suppressed: 0 bytes in 0 blocks
==7534==
==7534== For counts of detected and suppressed errors, rerun with: -v
==7534== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)


So it looks like the Boost serialization archive needs to create a pointer under covers to the LogParams derived type which makes sense, but why does this pointer not get cleaned up by Boost?

Answer

You hold a pointer to LogParams by an std::shared_ptr<Params> and Params doesn't have a virtual destructor (see Polymorphism and Destructor).

It's an undefined behavior

Comments