Ошибка пользовательского распределителя с контейнером STL

Я создал специальный распределитель, который выделяет память при построении и освобождает ее при уничтожении. (чтобы обеспечить быстрое выделение / освобождение). Когда я использую его с контейнером 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); by 'containerB.emplace_front ();', программа работает. Если я заменю containerB.assign (10, i); by 'containerB.insert (containerB.begin (), 10, i);', произойдет сбой программы. Не понимаю почему ...

Ошибка с clang и gcc: free (): сбой поврежденных несортированных фрагментов (дамп ядра)

Ошибка с gdb: free (): поврежденные несортированные фрагменты

Программа получила сигнал SIGABRT, прервано. __GI_raise (sig = sig @ entry = 6) в ../sysdeps/unix/sysv/linux/raise.c:51 51 ../sysdeps/unix/sysv/linux/raise.c: Нет такого файла или каталога.

ОБНОВИТЬ:

С лучшим оператором == у меня теперь есть SIGSEGV: программа получила сигнал SIGSEGV, ошибка сегментации. 0x000055555555537a в std :: __ cxx11 :: _ List_base> :: _ M_clear (this = 0x7fffffffdcd0) в /usr/include/c++/8/bits/list.tcc:74 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)

person dj4567    schedule 04.01.2019    source источник
comment
Авария как в? выбросить исключение?   -  person Matthieu Brucher    schedule 04.01.2019
comment
Fyi, void вместо T в вашем size_t конструкторе аргументов приводит в лучшем случае к обнадеживающему поведению. Такие же проблемы случаются и в других местах. Я откровенно шокирован, что ваш компилятор не выдает вам предупреждений (которые следует рассматривать как ошибки), в отличие от моего.   -  person WhozCraig    schedule 04.01.2019
comment
Я заменил for на: for (auto ptr = static_cast ‹T *› (m_data) + (size - 1); ptr ›= static_cast‹ T * ›(m_data); ptr--) m_free.push (ptr) ; Но не работает.   -  person dj4567    schedule 04.01.2019
comment
Сбой программы: SIGABRT   -  person dj4567    schedule 04.01.2019
comment
Вы пробовали отладку, например, чтобы увидеть, как на самом деле вызываются ваши функции выделения и освобождения? Или работает под таким инструментом, как valgrind?   -  person aschepler    schedule 04.01.2019
comment
valgrind не обнаруживает утечек, и результат gdb: Программа получила сигнал SIGABRT, прервано. __GI_raise (sig = sig @ entry = 6) в ../sysdeps/unix/sysv/linux/raise.c:51 51 ../sysdeps/unix/sysv/linux/raise.c: Нет такого файла или каталога. Я добавил печать в каждый метод / конструктор / деструктор, программа не дает сбой внутри функции распределителя.   -  person dj4567    schedule 04.01.2019


Ответы (3)


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 не влияет на действительность итераторов и ссылок. Но и assign, и clear аннулируют все ссылки, указатели и итераторы, относящиеся к элементам контейнера.
Таким образом, программа работает с emplace_front и вылетает с assign.

person P.W    schedule 04.01.2019
comment
@ dj4567: Отредактировал ответ, указав возможную причину сбоя. - person P.W; 04.01.2019
comment
Да, читал. Назначение всегда выполняется для пустого контейнера, поскольку для каждого цикла containerB очищается. Так что я не понимаю, почему: / Код простой, и я потратил много времени на его отладку ... Это странно. когда я вызываю метод assign, код вызывает конструктор распределителя, мне это показалось странным. Почему метод assign создает новый распределитель? : O - person dj4567; 04.01.2019
comment
Если я заменю containerB.assign (10, i); by 'containerB.insert (containerB.begin (), 10, i);', это не работает. Таким образом, проблема не возникает из-за недействительности итератора или ссылки, поскольку метод вставки не делает недействительным итератор и ссылку. - person dj4567; 04.01.2019
comment
@ dj4567: Похоже, в вашем коде больше проблем. Я попытался выполнить отладку и обнаружил, что деструкторы вызываются еще до выполнения for циклов. См. Это: wandbox.org/permlink/ISFklTE5ut8PqwiC. И они называются позже. - person P.W; 04.01.2019
comment
Да, потому что std :: list создает новый распределитель для преобразования CustomAllocator ‹size_t› в CustomAllocator ‹td :: _ List_node‹ long unsigned int ››. Итак, у нас есть конструктор копирования и конструктор перемещения. Я обновил свой код, теперь у меня ошибка сегментации вместо SIGABRT. Я полагаю, что std :: list не любит итератор, который всегда не равен, поэтому я обновил оператор ==. Я не вижу ничего странного в своем коде ... Я не понимаю :( - person dj4567; 04.01.2019
comment
Теперь, в обновленном коде, если я прокомментирую удаление оператора :: в деструкторе, все работает. В противном случае я получаю SIGSEGV ... Это очень странно! - person dj4567; 04.01.2019
comment
посмотрите это: метод deallocate вызывается со странным ptr: wandbox.org/permlink/ySGO6nbdPn6RyjPk Что делать ты думаешь об этом? Возможно: распределитель сравнивает равные, поэтому список использует распределитель для освобождения указателя, выделенного другим распределителем. Позже распределитель удаляется, поэтому у другого распределителя есть висячий указатель? Вывод: распределитель пула не работает с stl? Я не знаю ... Если у вас есть идея получше ... - person dj4567; 04.01.2019
comment
Посмотрите также это: wandbox.org/permlink/ZwsM8PihICRofs9s Я не знаю, как это исправить: O - person dj4567; 04.01.2019

Распределители, начиная с C ++ 11, могут иметь состояние.

Но распределители скопированы из того же общего состояния распределителя, так что что-то, выделенное исходным распределителем, должно освобождаться от копирования.

Также operator == и operator! = Должны сравнивать состояние, распределители не должны быть равными, если состояние отличается.

Это может быть реализовано с помощью указателя на потенциально общий пул в вашем распределителе, а не напрямую в распределителе. (Пул общего распределителя иногда называют «ареной»).

До C ++ 11 у распределителей вообще не могло быть состояния. Таким образом, объекты могут быть выделены / освобождены даже с помощью распределителя, созданного по умолчанию, который каждый раз может быть другим экземпляром. В этом случае ваши операторы равенства верны, но вам нужен один глобальный пул, а не возможность отдельного пула для каждого контейнера.

person Alex Guteniev    schedule 04.01.2019
comment
Я могу поместить пустоту * и стек в структуру и управлять указателем на эту структуру? - person dj4567; 05.01.2019
comment
да, если разные копии одного и того же распределителя будут обращаться к одной и той же структуре. - person Alex Guteniev; 05.01.2019
comment
Но распределитель ‹T›, который использует распределитель копий распределителя ‹T2›, не будет использовать одну и ту же структуру. Правильно? - person dj4567; 05.01.2019
comment
Да, у распределителей для разных типов может не быть общей структуры, поскольку структура, выделенная распределителем ‹T›, будет освобождена некоторым распределителем ‹T›, но не распределителем ‹T2›. Однако они также могут разделять структуру. - person Alex Guteniev; 05.01.2019
comment
В этом случае мне нужно иметь своего рода глобальную карту, которая содержит каждый пул для каждого типа ... Но поскольку несколько распределителей T будут использовать один и тот же пул T, у меня есть 2 варианта: 1 / Добавить мьютекс в общий пул, но в этом случае эффективность пула теряется ... поэтому пул становится бесполезным. 2 / Не используйте потокобезопасность, но в этом случае я не могу использовать несколько распределителей ‹T› в многопоточном контексте. Верно? Есть идея получше? - person dj4567; 05.01.2019
comment
Если ваш компилятор / STL совместим с C ++ 11, тогда у распределителя может быть состояние. Итак, два экземпляра распределителя ‹T› должны иметь один и тот же пул, только если один является копией другого (или один был назначен другому). В противном случае два экземпляра распределителя ‹T› могут иметь разные пулы. Итак, вам нужно только убедиться, что копия распределителя ‹T› копирует указатель на пул (что может быть достигнуто даже с помощью конструктора копирования по умолчанию). - person Alex Guteniev; 06.01.2019
comment
До C ++ 11 вам действительно нужно было использовать пул для каждого типа. - person Alex Guteniev; 06.01.2019
comment
А если у вас C ++ 17, вам даже не нужно реализовывать собственный распределитель. Вы просто реализуете свой пул, наследуя std::pmr::memory_resource и передавая его контейнерам, которые будут использовать std::pmr::polymorphic_allocator. Контейнеры с этим распределителем доступны в пространстве имен std :: pmr. - person Alex Guteniev; 06.01.2019
comment
Я использую C ++ 17 и знаю memory_resource. Но у меня проблема с ресурсом памяти. В настоящее время у меня есть пул объектов (например, пул size_t, пул char). Я хотел бы использовать memory_resource, но не могу. Конструктор копирования std :: polymorphic_allocator (используется с std :: memory_resource) do: создает polymorphic_allocator, используя other.resource () в качестве базового ресурса памяти. В этом случае у меня не может быть такого пула. Чтобы использовать memory_resource, memory_resource может разрешить любой размер (мой текущий пул может выделять только размер типа). - person dj4567; 06.01.2019
comment
Polymorphic_allocator ‹T› будет использовать тот же memory_resource, если он скопирован из polymorphic_allocator ‹T2›. Так что мой текущий пул не может работать с этой системой. Верно? - person dj4567; 06.01.2019
comment
да. Тогда вам не следует использовать memory_resource. На самом деле, я понял, что было заблуждением сказать, что это можно реализовать, указав на потенциально общий пул в вашем распределителе, а не напрямую имея пул в распределителе. Да, это обычно делается, но вы не обязаны это делать. Поскольку вы определили propagate_on_container_* traits для false_type, у вас может быть пул непосредственно в распределителе. Тем не менее, вам нужно исправить операторы == и! =, Они должны быть равны, только если равен instance, а не typeid. - person Alex Guteniev; 08.01.2019

После этого обсуждения я реализовал аллоатор с полным состоянием, но у меня проблема с std :: list. Я разместил вопрос об этом: Контейнер STL не поддерживает распределитель с отслеживанием состояния?

person dj4567    schedule 11.01.2019