STL 容器的自定义分配器错误

Posted

技术标签:

【中文标题】STL 容器的自定义分配器错误【英文标题】:Custom allocator bug with STL container 【发布时间】:2019-01-04 09:42:43 【问题描述】:

我创建了一个自定义分配器,它在构造时分配内存并在销毁时释放它。 (为了允许快速分配/解除分配)。 当我将它与 STL 容器一起使用时,一切正常!当我使用assign方法时预期... 我不明白为什么...

我尝试打印每个分配/空闲的指针,但一切看起来都不错。

#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;

实际上程序崩溃了。 如果我评论 'containerB.assign(10, i);',程序就可以工作。 如果我替换 'containerB.assign(10, i);'通过 'containerB.emplace_front();',程序运行。 如果我替换 'containerB.assign(10, i);'通过 'containerB.insert(containerB.begin(), 10, i);',程序崩溃。 我不明白为什么...

clang 和 gcc 错误: free():损坏的未排序块 中止(核心转储)

gdb 错误: free(): 损坏的未排序块

程序收到信号 SIGABRT,已中止。 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51 51 ../sysdeps/unix/sysv/linux/raise.c:没有这样的文件或目录。

更新:

有了更好的运算符 ==,我现在有了一个 SIGSEGV: 程序收到信号 SIGSEGV,分段错误。 /usr/include/c++/8/bits/list.tcc:74 的 std::__cxx11::_List_base >::_M_clear (this=0x7fffffffdcd0) 中的 0x000055555555537a 74 __cur = __tmp->_M_next;

valgrind 输出:

==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)

【问题讨论】:

崩溃?抛出异常? 仅供参考,void for T 在您的 size_t 参数构造函数中会导致最好的行为。同样的问题也发生在其他地方。坦率地说,我很震惊你的编译器没有像我的那样向你发出警告(应该被视为错误)。 我已将 for 替换为:for (auto ptr = static_cast(m_data) + (size - 1); ptr >= static_cast(m_data); ptr- -) m_free.push(ptr);但不工作。 程序崩溃:SIGABRT 您是否尝试过调试,例如查看您的 allocate 和 deallocate 函数是如何实际调用的?还是在 valgrind 之类的工具下运行? 【参考方案1】:

m_data 是指向void 的指针。下一行CustomAllocator的构造函数中对m_data进行了指针运算。

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

根据标准,void* 上的指针算术格式不正确。这可能会导致未定义的行为。

编辑 OP 已更新,不再对void* 执行指针运算。所以我们必须找到导致崩溃的其他原因。 对于list 容器emplace_front 不影响迭代器和引用的有效性。但是assignclear 都使所有引用容器元素的引用、指针和迭代器无效。 所以程序在emplace_front 下运行,在assign 下崩溃。

【讨论】:

@dj4567:相应地编辑了答案,提供了崩溃的可能原因。 是的,我读过。分配总是在空容器上执行,因为对于每个循环,都会清除 containerB。所以我不明白为什么:/代码很简单,我花了很多时间来调试它......它很奇怪。当我调用 assign 方法时,代码调用了分配器构造函数,我发现这很奇怪。为什么 assign 方法会创建新的分配器? :O 如果我替换 'containerB.assign(10, i);'通过'containerB.insert(containerB.begin(), 10, i);',它不起作用。所以问题不是来自迭代器或引用无效,因为插入方法不会使迭代器和引用无效。 @dj4567:您的代码中似乎还有更多问题。我尝试调试,发现在执行for 循环之前就调用了析构函数。看到这个:wandbox.org/permlink/ISFklTE5ut8PqwiC。它们也会在以后被调用。 是的,因为 std::list 创建了一个新的分配器,以便将 CustomAllocator 转换为 CustomAllocator<:_list_node unsigned int>>。所以我们有一些复制构造函数和移动构造函数被调用。我已经更新了我的代码,现在我有一个分段错误而不是 SIGABRT。我想 std::list 不喜欢总是不相等的迭代器,所以我更新了 operator==。我在我的代码中没有看到任何奇怪的东西......我不明白:(【参考方案2】:

自 C++11 以来的分配器可以有状态。

但是从同一个分配器复制的分配器共享状态,因此原始分配器分配的东西应该可以通过复制释放。

operator== 和 operator!= 也应该比较状态,如果状态不同,分配器不应该相等。

这可以通过在分配器中使用指向潜在共享池的指针来实现,而不是直接在分配器中使用池。 (共享分配器的池有时称为“竞技场”)。

在 C++11 之前,分配器根本没有状态。这样即使使用默认构造的分配器也可以分配/释放对象,每次都可能是不同的实例。在这种情况下,您的相等运算符是正确的,但您需要有一个全局池,而不是为每个容器设置单独的池。

【讨论】:

我可以把 void* 和 stack 放到一个结构体中并管理这个结构体上的指针吗? 可以,只要同一个分配器的不同副本访问同一个结构。 但是使用 allocator 的复制分配器的分配器 不会共享相同的结构。对吗? 是的,不同类型的分配器可能没有共享结构,因为分配器分配的结构将被某些分配器释放,而不是分配器。但是,它们也可以共享结构。 在这种情况下,我需要一种包含每种类型的每个池的全局映射...但是由于 T 的多个分配器将使用同一个池 T,我有 2 个选项:1/在共享池中添加一个互斥体,但是在这种情况下,池的效率会丢失......所以池变得无用。 2/ 保持非线程安全,但在这种情况下,我不能在多线程上下文中使用多个 Allocator。对?你有更好的主意吗?【参考方案3】:

经过讨论,我实现了一个有状态的分配器,但我对 std::list 有疑问。 我发布了一个问题:STL container don't support statefull allocator?

【讨论】:

以上是关于STL 容器的自定义分配器错误的主要内容,如果未能解决你的问题,请参考以下文章

STL容器自定义内存分配器

STL容器自定义内存分配器

STL容器自定义内存分配器

自定义C++ STL内存分配器

使自定义容器可迭代

令人信服的自定义C ++分配器示例?