I have recently learned about custom allocators in C++11 and am trying to use them in my application. However I am running into a segfault, which can be reproduced by the following minimal example:
#include <vector>
#include <unordered_map>
#include <scoped_allocator>
template<class T> struct custom_allocator {
typedef T value_type;
custom_allocator() noexcept {}
template <class U> custom_allocator (const custom_allocator<U>&) noexcept {}
T* allocate (std::size_t n) { return static_cast<T*>(::operator new(n*sizeof(T))); }
void deallocate (T* p, std::size_t n) { ::delete(p); }
template <typename U> constexpr bool operator==(const custom_allocator<U>&) const { return true; }
template <typename U> constexpr bool operator!=(const custom_allocator<U>&) const { return false; }
};
template<class T> using custom_scoped_allocator = std::scoped_allocator_adaptor<custom_allocator<T> >;
typedef std::unordered_map<int, int, std::hash<int>, std::equal_to<int>,
custom_scoped_allocator<std::pair<const int, int> > > MyMap;
typedef std::vector<MyMap, custom_scoped_allocator<MyMap> > MyVector;
int main() {
MyVector vec(1);
vec[0][0] = 0;
return 0;
}
The custom allocator is exactly the one suggested in the example at
http://www.cplusplus.com/reference/memory/allocator_traits/
It is then combined with std::scoped_allocator_adaptor
as I have seen suggested in a few places on the internet.
The code compiles fine with gcc 5.4.0 using g++ -g3 -O0 -std=c++11 -march=core-avx-i -o tmpalloc tmpalloc.cpp
. When I attempt to run it, a segfault is reported starting with
*** Error in `./tmpalloc': double free or corruption (fasttop): 0x0000000000ae3c90 ***
If I recompile using AddressSanitizer, I get the following details about the double free: https://pastebin.com/raw/xy2NQtD0 (Sorry for the pastebin but it is not pretty.) Basically, the destructor for an unordered_map object seems to be called twice.
What am I doing wrong?
Your allocate
function allocates a bunch of bytes, but does not call any constructors (by directly invoking the operator new
global function). Your deallocate
function invokes delete
on a typed pointer, which results in the destructor for that type being called before the global operator delete
function is called to free the memory. The end result is that you destruct memory that you did not construct (the construction and destruction are handled by the caller after/before your allocate
/deallocate
functions are called.
You should be releasing the memory using a complementary method to the way you allocate it. In C++14 or later, this would be:
void deallocate (T* p, std::size_t n) { ::operator delete(p, n * sizeof(T)); }
where n
is value passed to allocate
, and the second parameter is the value passed to new
. Prior to C++14, just relay to the single parameter version (which is what the default two parameter operator delete will do):
void deallocate (T* p, std::size_t n) { ::operator delete(p); }