mercredi 2 septembre 2020

list.sort fails with abort when list is created with stateful allocator when compiled with gcc

Code below runs fine when compiled with VC compiler, but fail with abort inside list.sort on Linux when compiled with gcc (any recent version like 7.5 to 10.2).

The reason why abort is called is because splice method used in list.sort compares 2 allocators (which is correct) and they don't match. One of them is what I pass into list constructor (with custom state), another is created by STL using default constructor.

My question - is this correct behavior of C++ library to use allocator's default constructor as opposed to using instance which I provide for item allocation or create it with copying constructor?

UPDATE: As T.C. mentioned in comment this is known libstdc++ bug 66742.

#include <iostream>
#include <list>
#include <cassert>

using namespace std;

template <typename T>
class my_allocator
{
public:
    using value_type = typename std::remove_const<T>::type;
    using pointer = value_type*;
    using size_type = size_t;

    typedef std::true_type propagate_on_container_copy_assignment;
    typedef std::true_type propagate_on_container_move_assignment;
    typedef std::true_type propagate_on_container_swap;

    template<typename U>
    struct rebind {
        typedef my_allocator<U> other;
    };

    my_allocator() : m_internalState(0)
    {
        cout << "my_allocator(): internal state=" << m_internalState << endl;
    }

    my_allocator(const void* pAllocator) : m_internalState(pAllocator)
    {
        cout << "my_allocator(const void * pAllocator): internal state=" << m_internalState << endl;
    }

    template <typename U>
    my_allocator(const my_allocator<U>& myAllocator) noexcept : m_internalState(myAllocator.m_internalState)
    {
        cout << "my_allocator(const my_allocator<U>& myAllocator): internal state=" << m_internalState << endl;
    }

    pointer allocate(size_type count, const void* = nullptr)
    {
        return (pointer)malloc(sizeof(T) * count);
    }

    void deallocate(pointer p, size_type count = 0)
    {
        free(p);
    }

    bool operator==(const my_allocator& other) const
    {
        cout << "operator==(const my_allocator& other): this=" << m_internalState
            << ", other=" << other.m_internalState << endl;
        return m_internalState == other.m_internalState;
    }

    bool operator!=(const my_allocator& other) const
    {
        return !(this->operator==(other));
    }
private:
    template <typename U>
    friend class my_allocator;

    const void* m_internalState;
};

int main()
{
    // all asserts below are true
    //using X = my_allocator<int>;
    //using Y = my_allocator<long>;
    //Y b((void*)1);
    //X a(b);
    //assert(Y(a) == b);
    //assert(a == X(b));
    //assert(a == b);

    cout << "Create list" << endl;

    auto allocator = my_allocator<int>((void*)2);
    auto l2 = std::list<int, my_allocator<int>>(allocator);

    l2.emplace_back(2);
    l2.emplace_back(1);

    cout << "Sort list" << endl;
    l2.sort(); // Abort is called here

    cout << "Success" << endl;
    return 0;
}

C++ (vc++) output (as provided by running this code on https://rextester.com/l/cpp_online_compiler_visual

Create list
my_allocator(const void * pAllocator): internal state=0000000000000002
my_allocator(const my_allocator<U>& myAllocator): internal state=0000000000000002
Sort list
operator==(const my_allocator& other): this=0000000000000002, other=0000000000000002
Success

C++ (gcc) output

Error(s):

Abort signal from abort(3) (SIGABRT)

Create list
my_allocator(const void * pAllocator): internal state=0x2
my_allocator(const my_allocator<U>& myAllocator): internal state=0x2
Sort list
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
my_allocator(): internal state=0
operator==(const my_allocator& other): this=0, other=0x2

Stack trace when abort is called

>   std::__cxx11::list<int, my_allocator<int> >::_M_check_equal_allocators(std::__cxx11::list<int, my_allocator<int> > * const this, std::__cxx11::list<int, my_allocator<int> > & __x) Line 1829
    std::__cxx11::list<int, my_allocator<int> >::splice(std::__cxx11::list<int, my_allocator<int> > * const this, std::__cxx11::list<int, my_allocator<int> >::const_iterator __position, std::__cxx11::list<int, my_allocator<int> > & __x, std::__cxx11::list<int, my_allocator<int> >::const_iterator __i) Line 1480
    std::__cxx11::list<int, my_allocator<int> >::splice(std::__cxx11::list<int, my_allocator<int> > * const this, std::__cxx11::list<int, my_allocator<int> >::const_iterator __position, std::__cxx11::list<int, my_allocator<int> > & __x, std::__cxx11::list<int, my_allocator<int> >::const_iterator __i) Line 1502
    std::__cxx11::list<int, my_allocator<int> >::sort(std::__cxx11::list<int, my_allocator<int> > * const this) Line 481
    main() Line 90

Aucun commentaire:

Enregistrer un commentaire