简体   繁体   中英

Custom allocator bug with STL container

I created a custom allocator that allocate memory on construction and release it on destruction. (in order to allow fast allocation/deallocation). When I use it with STL container, all works fine! Expected when I use the assign method... I don't understand why...

I've tried to print each pointer allocated / free, but all looks good.

#include <cstddef>
#include <type_traits>
#include <stack>
#include <numeric>
#include <list>

template <class T>
class CustomAllocator
{
    public:
        using value_type = T;
        using size_type = std::size_t;
        using difference_type = std::ptrdiff_t;
        using propagate_on_container_copy_assignment = std::false_type;
        using propagate_on_container_move_assignment = std::false_type;
        using propagate_on_container_swap = std::false_type;
        using is_always_equal = std::false_type;

        CustomAllocator();
        CustomAllocator(size_type size);
        ~CustomAllocator();

        CustomAllocator(const CustomAllocator&);
        CustomAllocator& operator=(const CustomAllocator&) = delete;
        CustomAllocator(CustomAllocator&& src)
            : m_data(std::move(src.m_data)) , m_free(std::move(src.m_free))
        {
            src.m_data = nullptr;
        }


        CustomAllocator& operator=(CustomAllocator&&) = delete;

        template <class T2>
        CustomAllocator(const CustomAllocator<T2>&);

        template <class T2>
        bool operator==(const CustomAllocator<T2>&) const noexcept;

        template <class T2>
        bool operator!=(const CustomAllocator<T2>&) const noexcept;

        value_type* allocate(size_type);
        void deallocate(value_type* ptr, size_type);

    private:
        template <class>
        friend class CustomAllocator;

        void* m_data = nullptr;
        std::stack<void*> m_free;
};

template <class T>
CustomAllocator<T>::CustomAllocator() : CustomAllocator(1024)
{
}

template <class T>
CustomAllocator<T>::CustomAllocator(size_type size)
{
    m_data = ::operator new(sizeof(T) * size);

    for (auto ptr = static_cast<T*>(m_data) + (size - 1); ptr >= 
         static_cast<T*>(m_data); ptr--)
        m_free.push(ptr);
}

template <class T>
CustomAllocator<T>::CustomAllocator(const CustomAllocator&)
    : CustomAllocator(1024)
{
}

template <class T>
template <class T2>
CustomAllocator<T>::CustomAllocator(const CustomAllocator<T2>&)
    : CustomAllocator(1024)
{
}

template <class T>
CustomAllocator<T>::~CustomAllocator()
{
    if (m_data)
        ::operator delete(m_data);
}

template <class T>
template <class T2>
inline bool CustomAllocator<T>::
operator==(const CustomAllocator<T2>&) const noexcept
{
    return typeid(T) == typeid(T2);
}

template <class T>
template <class T2>
inline bool CustomAllocator<T>::
operator!=(const CustomAllocator<T2>&) const noexcept
{
     return typeid(T) != typeid(T2);
}

template <class T>
typename CustomAllocator<T>::value_type*
CustomAllocator<T>::allocate(size_type size)
{
    if (m_free.empty() || size != 1)
        throw std::bad_alloc();

    auto ptr = m_free.top();

    m_free.pop();

    return reinterpret_cast<value_type*>(ptr);
}

template <class T>
void CustomAllocator<T>::deallocate(value_type* ptr, size_type)
{
    m_free.push(ptr);
}

int main()
{
    std::list<size_t, CustomAllocator<size_t>> containerA;
    std::list<size_t, CustomAllocator<size_t>> containerB;


    for (size_t i = 0; i < 10; ++i)
    {
        for (size_t j = 0; j < 100; ++j)
            containerA.emplace_front();

        // dont works with this
        containerB.assign(10, i);

        containerA.clear();
        containerB.clear();
    }

    return 0;
}

Actually the program crash. If I comment 'containerB.assign(10, i);', the program works. If I replace 'containerB.assign(10, i);' by 'containerB.emplace_front();', the program works. If I replace 'containerB.assign(10, i);' by 'containerB.insert(containerB.begin(), 10, i);', the program crash. I don't understand why...

Error with clang and gcc: free(): corrupted unsorted chunks abort (core dumped)

Error with gdb: free(): corrupted unsorted chunks

Program received signal SIGABRT, Aborted. __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51 51 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.

UPDATE:

With better operator ==, I have now a SIGSEGV: Program received signal SIGSEGV, Segmentation fault. 0x000055555555537a in std::__cxx11::_List_base >::_M_clear (this=0x7fffffffdcd0) at /usr/include/c++/8/bits/list.tcc:74 74 __cur = __tmp->_M_next;

The valgrind output:

==17407== Memcheck, a memory error detector
==17407== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==17407== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==17407== Command: ./a.out
==17407== 
==17407== Invalid read of size 8
==17407==    at 0x10937A: std::__cxx11::_List_base<unsigned long, CustomAllocator<unsigned long> >::_M_clear() (list.tcc:74)
==17407==    by 0x109287: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::clear() (stl_list.h:1507)
==17407==    by 0x108F0C: main (bug.cpp:154)
==17407==  Address 0x5b93b00 is 0 bytes inside a block of size 24,576 free'd
==17407==    at 0x4C3123B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17407==    by 0x1091CE: CustomAllocator<std::_List_node<unsigned long> >::~CustomAllocator() (bug.cpp:100)
==17407==    by 0x109107: std::__cxx11::_List_base<unsigned long, CustomAllocator<unsigned long> >::_List_impl::~_List_impl() (stl_list.h:382)
==17407==    by 0x109205: std::__cxx11::_List_base<unsigned long, CustomAllocator<unsigned long> >::~_List_base() (stl_list.h:506)
==17407==    by 0x10915B: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::~list() (stl_list.h:834)
==17407==    by 0x109B66: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::insert(std::_List_const_iterator<unsigned long>, unsigned long, unsigned long const&) (list.tcc:122)
==17407==    by 0x109586: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::_M_fill_assign(unsigned long, unsigned long const&) (list.tcc:300)
==17407==    by 0x10926C: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::assign(unsigned long, unsigned long const&) (stl_list.h:897)
==17407==    by 0x108EEE: main (bug.cpp:151)
==17407==  Block was alloc'd at
==17407==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17407==    by 0x109665: CustomAllocator<std::_List_node<unsigned long> >::CustomAllocator(unsigned long) (bug.cpp:76)
==17407==    by 0x10A3B6: CustomAllocator<std::_List_node<unsigned long> >::CustomAllocator<unsigned long>(CustomAllocator<unsigned long> const&) (bug.cpp:92)
==17407==    by 0x109FFE: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::list(unsigned long, unsigned long const&, CustomAllocator<unsigned long> const&) (stl_list.h:717)
==17407==    by 0x109B0B: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::insert(std::_List_const_iterator<unsigned long>, unsigned long, unsigned long const&) (list.tcc:122)
==17407==    by 0x109586: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::_M_fill_assign(unsigned long, unsigned long const&) (list.tcc:300)
==17407==    by 0x10926C: std::__cxx11::list<unsigned long, CustomAllocator<unsigned long> >::assign(unsigned long, unsigned long const&) (stl_list.h:897)
==17407==    by 0x108EEE: main (bug.cpp:151)
==17407== 
==17407== 
==17407== HEAP SUMMARY:
==17407==     in use at exit: 0 bytes in 0 blocks
==17407==   total heap usage: 504 allocs, 504 frees, 668,800 bytes allocated
==17407== 
==17407== All heap blocks were freed -- no leaks are possible
==17407== 
==17407== For counts of detected and suppressed errors, rerun with: -v
==17407== ERROR SUMMARY: 100 errors from 1 contexts (suppressed: 0 from 0)

m_data is a pointer to void . There is pointer arithmetic performed on m_data in the constructor of CustomAllocator in the following line.

for (auto ptr = m_data + (sizeof(T) * (size - 1)); ptr >= m_data; ptr -= sizeof(T))

As per the standard, pointer arithmetic on void* is ill-formed. This could be leading to undefined behavior.

EDIT
The OP has been updated and there is no more pointer arithmetic performed on a void* . So we have to find other causes for the crash.
For list container emplace_front does not affect the validity of iterators and references. But both assign and clear invalidates all references, pointers and iterators referring to the elements of the container.
So the program works with emplace_front and crashes with assign .

Allocators since C++11 can have state.

But allocators copied from the same allocator share state, so that something allocated by original allocator should deallocate fine with copy.

Also operator== and operator!= should compare the state, allocators should not be equal if the state is different.

This can be implemented by having a pointer to potentially shared pool in your allocator rather than directly having pool in allocator. (Shared allocator's pool is sometimes called 'arena').

Before C++11, allocators could not have state at all. So that objects can be allocated/deallocated even with default-constructed allocator, which may be different instance each time. In this case your equality operators are correct, but you need to have one global pool, rather than possibility for separate pool for each container.

After this discussion, I have implemented a statefull alloator, but I have issue with std::list. I posted a question about that: STL container don't support statefull allocator?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM