自定义分配器- Microsoft std::map实现对相同的元素进行两次重新分配,GCC工作得很好

custom allocator - microsoft std::map implementation deallocates the same element twice, gcc works fine

本文关键字:两次 新分配 分配 很好 工作 GCC std map Microsoft 分配器 实现      更新时间:2023-10-16

我们的想法是有一个可重用的仍然兼容的分配器,它易于使用,并且不会在软件的性能关键区域使用自由存储。

下面是一些测试代码(非常基本的测试):
#include <map>
#include <iostream>
#include "CustomPoolAllocator.h"
typedef std::map<int, int>::value_type mymap_vtype;
typedef CustomPoolAllocator<mymap_vtype, 200> my_alloc_type;
int main()
{
    std::map<int, int, std::less<int>, my_alloc_type> mymap;
    int sign = 1;
    for (int i = 0; i < 90; ++i)
    {
        mymap[i * sign] = i;
        sign *= -1;
    }
    for (auto & elem : mymap)
        std::cout << elem.first << " " << elem.second << std::endl;
    mymap.clear();
    for (int i = 0; i < 90; ++i)
    {
        mymap[i * sign] = i;
        sign *= -1;
    }
    for (auto & elem : mymap)
        std::cout << elem.first << " " << elem.second << std::endl;
    return 0;
}

看起来这个测试足以发现一些问题。我用MinGW和visual studio 2013进行了测试。

不确定这是否很重要,但g++ --version打印的是:

g++ (x86_64-posix-seh-rev1,由MinGW-W64项目构建)4.9.2版权所有:自由软件基金会,Inc。这是免费的软件;有关复制条件,请参阅源代码。没有保修;甚至不是为了适销性或适合某一特定产品目的。

在Visual-Studio中,持续地将前23个元素添加到映射中,效果很好。

在将第24个元素添加到map后,有一个元素的释放(我不清楚为什么),然后相同的地址立即再次被释放(通过断点验证,逐步调试)。

当然,程序到达abort()并打印:

SingleSizeAlloc:超出范围元素或被释放两次!索引:24,池中元素:200,元素大小:24

如果我删除abort(),会有一些我不明白的异常:std::length_error




当使用MinGW构建时,它按预期工作,没有问题。

谁能告诉我发生了什么事?很多谢谢!

可重用分配器模板的代码在这里:

#ifndef _CustomPoolAllocator_H_
#define _CustomPoolAllocator_H_
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
// This is a very simple fixed-size block allocator based on a free-list.
class SingleSizeAlloc
{
    size_t size;
    const size_t num;
    const size_t num_illegal;
    char *buf;
    size_t listHead;
    size_t *list;
public:
    SingleSizeAlloc(size_t s, size_t n) :
            size(s), num(n), num_illegal(n + 1)
    {
        buf = new char[size * num];
        list = new size_t[num];
        listHead = 0;
        for (unsigned i = 0, j = 1; i < n; ++i, ++j)
        {
            list[i] = j;
        }
    }
    ~SingleSizeAlloc()
    {
        delete[] buf;
        delete[] list;
        listHead = 0;
        list = 0;
    }
    size_t getSize() const
    {
        return size;
    }
    void *allocate()
    {
        void *allocated = NULL;
        if (listHead == num)
        {
            fprintf(stderr, "SingleSizeAlloc: ERROR - no memory left!n");
            abort();
        }
        else
        {
            size_t idx = listHead;
            if (num_illegal == list[idx])
            {
                fprintf(stderr,
                        "SingleSizeAlloc: ERROR - allocated same element twice! idx: %u, size: %u, num elements in pool: %un",
                        (unsigned) idx, (unsigned) size, (unsigned) num);
                abort();
            }
            listHead = list[idx];
            allocated = buf + idx * size;
            list[idx] = num_illegal;
        }
        return allocated;
    }
    void deallocate(void *p)
    {
        size_t index = ((char *) p - buf) / size;
        if (num_illegal == list[index])
        {
            list[index] = listHead;
            listHead = index;
        }
        else
        {
            fprintf(stderr,
                    "SingleSizeAlloc: out of range Element or deallocated twice! index: %u, elements in pool: %u, element size: %un",
                    (unsigned) index, (unsigned) num, (unsigned) size);
            abort();
        }
    }
};

// Contains up-to 16 simple free-list allocators where each supports a different block size.
// Each related STL-Compatible allcator object will have a pointer to this reference-counted object.
// When the last STL-Compatible allcator object is destroyed, this object will be destroyed too.
class FixedSizeAllocator
{
    enum
    {
        MAX_DIFFERENT_SIZES = 16
    };
    struct IsSize
    {
        size_t s;
        IsSize(size_t s) :
                s(s)
        {
        }
        bool operator()(const SingleSizeAlloc* p) const
        {
            return (s == p->getSize());
        }
    };
    SingleSizeAlloc *pools[MAX_DIFFERENT_SIZES];
    const size_t eachPoolSize;
    int refcounter;
    int numPools;
public:
    FixedSizeAllocator(size_t n) :
            eachPoolSize(n), refcounter(0), numPools(0)
    {
        memset(pools, 0, sizeof(pools));
    }
    ~FixedSizeAllocator()
    {
        SingleSizeAlloc **pp = pools;
        for (int i = 0; i < numPools && *pp; ++i, ++pp)
        {
            delete *pp;
            *pp = 0;
        }
    }
    void incRefCounter()
    {
        refcounter++;
    }
    void decRefCounter()
    {
        refcounter--;
    }
    int getRefCounter() const
    {
        return refcounter;
    }
    void *allocate(size_t s, size_t n)
    {
        if (n > 1)
        {
            fprintf(stderr,
                    "FixedSizeAllocator doesn't support allocation of multiple elements (i.e. can't be used for containters such as vector...)");
            abort();
        }
        SingleSizeAlloc **pp = std::find_if(pools, pools + numPools, IsSize(s));
        int idx = pp - pools;
        if (idx >= numPools)
        {
            if (idx < MAX_DIFFERENT_SIZES)
            {
                *pp = new SingleSizeAlloc(s, eachPoolSize);
                numPools++;
            }
            else
            {
                fprintf(stderr,
                        "FixedSizeAllocator: ERROR - allocate error !! size: %u, different sizes in the allocator: %dn",
                        (unsigned) s, idx);
                abort();
            }
        }
        return (*pp)->allocate();
    }
    void deallocate(void *pObj, size_t size)
    {
        SingleSizeAlloc **pp = std::find_if(pools, pools + numPools,
                IsSize(size));
        int idx = pp - pools;
        if (idx >= numPools)
        {
            if (idx >= MAX_DIFFERENT_SIZES)
            {
                fprintf(stderr,
                        "FixedSizeAllocator: ERROR - deallocate error!! size: %un",
                        (unsigned) size);
                abort();
            }
        }
        (*pp)->deallocate(pObj);
    }
};
// This is the STL compatible interface. it holds a pointer to the actual implementation of the allocator.
// Whenever this object is created from a related object (see the rebind), the reference counter for the
// allocator is incremented (decremented in d'tor).
// When the last of these is destroyed, the implementation object is also destroyed.
template<class T, size_t pool_size> class CustomPoolAllocator
{
private:
    CustomPoolAllocator &operator=(const CustomPoolAllocator &other);
public:
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T value_type;
    template<class U> struct rebind
    {
        typedef CustomPoolAllocator<U, pool_size> other;
    };
    FixedSizeAllocator *allocImp;
    CustomPoolAllocator()
    {
        // create a new FixedSizeAllocator
        allocImp = new FixedSizeAllocator(pool_size);
        allocImp->incRefCounter();
    }
    template<class Q>
    CustomPoolAllocator(const CustomPoolAllocator<Q, pool_size>& other)
    {
        allocImp = other.allocImp;
        allocImp->incRefCounter();
    }
    // This copy-c'tor was missing in the original code,
    // it's necessary, otherwise the ref-counter isn't incremented.
    CustomPoolAllocator(const CustomPoolAllocator& other)
    {
        allocImp = other.allocImp;
        allocImp->incRefCounter();
    }
    ~CustomPoolAllocator()
    {
        allocImp->decRefCounter();
        if (0 == allocImp->getRefCounter())
        {
            delete allocImp;
            allocImp = 0;
        }
    }
    pointer address(reference x) const
    {
        return &x;
    }
    const_pointer address(const_reference x) const
    {
        return &x;
    }
    pointer allocate(size_type n)
    {
        return (pointer) allocImp->allocate(sizeof(T), n);
    }
    void deallocate(pointer p, size_type n)
    {
        allocImp->deallocate(p, n * sizeof(T));
    }
    size_type max_size() const
    {
        return sizeof(T); // #### this is the problem!!! should return pool_size.
    }
    void construct(pointer p, const T& val)
    {
        new (p) T(val);
    }
    void destroy(pointer p)
    {
        p->~T();
    }
};
#endif // _CustomPoolAllocator_H_

单一大小的分配器看起来不适合

A = allocate();
B = allocate();
Free( A );
A = allocate();
C = allocate();

我认为头部移动了,不能处理被释放和重用的问题。

差异可能是由于优化了mingw以减少副本。

max_size()成员函数出了问题。

这就是数字24的来源(24 == sizeof(T))。这里有人给我指出来了,但我找不到出处。

这应该返回池中可以包含的元素的数量(即在本例中为pool_size)。

另外,关于默认拷贝c'tor的注释是非常正确的,并且值得编辑原始代码。

我仍然觉得很奇怪,容器释放相同的地址两次(microsoft bug?)

不管怎样,谢谢大家!

此外,这里有一个使用c++ 11特性的固定版本。它消除了实现任何析构函数和默认复制构造函数的需要。

#ifndef _CustomPoolAllocator_H_
#define _CustomPoolAllocator_H_
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <memory>
// This is a very simple fixed-size block allocator based on a free-list.
class SingleSizeAlloc
{
    size_t size;
    const size_t num;
    const size_t num_illegal;
    std::unique_ptr<char[]> buf;
    std::unique_ptr<size_t[]> list;
    size_t listHead;
    size_t * const listEnd;
public:
    SingleSizeAlloc(size_t size, size_t n) :
        size(size), num(n), num_illegal(n + 1), buf(new char[size * num]), list(
        new size_t[num]), listEnd(list.get() + num)
    {
        listHead = 0;
        unsigned i = 0;
        std::for_each(list.get(), listEnd, [&i](size_t &elem)
        {   elem = ++i; });
    }
    ~SingleSizeAlloc()
    {
        listHead = 0;
        list = 0;
    }
    size_t getSize() const
    {
        return size;
    }
    void *allocate()
    {
        void *allocated = NULL;
        if (listHead == num)
        {
            fprintf(stderr, "SingleSizeAlloc: ERROR - no memory left!n");
            abort();
        }
        else
        {
            size_t idx = listHead;
            if (num_illegal == list[idx])
            {
                fprintf(stderr,
                    "SingleSizeAlloc: ERROR - allocated same element twice! idx: %u, size: %u, num elements in pool: %un",
                    (unsigned)idx, (unsigned)size, (unsigned)num);
                abort();
            }
            listHead = list[idx];
            allocated = buf.get() + idx * size;
            list[idx] = num_illegal;
        }
        return allocated;
    }
    void deallocate(void *p)
    {
        size_t index = ((char *)p - buf.get()) / size;
        if (num_illegal == list[index])
        {
            list[index] = listHead;
            listHead = index;
        }
        else
        {
            fprintf(stderr,
                "SingleSizeAlloc: out of range Element or deallocated twice! index: %u, elements in pool: %u, element size: %un",
                (unsigned)index, (unsigned)num, (unsigned)size);
            abort();
        }
    }
};
// Contains up-to 16 simple free-list allocators where each supports a different block size.
// Each related STL-Compatible allcator object will have a pointer to this reference-counted object.
// When the last STL-Compatible allcator object is destroyed, this object will be destroyed too.
class FixedSizeAllocator
{
    enum
    {
        MAX_DIFFERENT_SIZES = 16
    };
    struct IsSize
    {
        size_t size;
        IsSize(size_t size) :
            size(size)
        {
        }
        bool operator()(const std::unique_ptr<SingleSizeAlloc>& p) const
        {
            return (size == p->getSize());
        }
    };
    std::unique_ptr<SingleSizeAlloc> pools[MAX_DIFFERENT_SIZES];
    const size_t eachPoolSize;
    int numPools;
public:
    FixedSizeAllocator(size_t n) :
        eachPoolSize(n), numPools(0)
    {
        memset(pools, 0, sizeof(pools));
    }
    void *allocate(size_t size, size_t n)
    {
        if (n > 1)
        {
            fprintf(stderr,
                "FixedSizeAllocator doesn't support allocation of multiple elements (i.e. can't be used for containters such as vector...)");
            abort();
        }
        auto pp = std::find_if(pools, pools + numPools, IsSize(size));
        int idx = pp - pools;
        if (idx >= numPools)
        {
            if (idx < MAX_DIFFERENT_SIZES)
            {
                (*pp).reset(new SingleSizeAlloc(size, eachPoolSize));
                numPools++;
            }
            else
            {
                fprintf(stderr,
                    "FixedSizeAllocator: ERROR - allocate error !! size: %u, different sizes in the allocator: %dn",
                    (unsigned)size, idx);
                abort();
            }
        }
        return (*pp)->allocate();
    }
    void deallocate(void *pObj, size_t size)
    {
        auto pp = std::find_if(pools, pools + numPools, IsSize(size));
        int idx = pp - pools;
        if (idx >= numPools)
        {
            if (idx >= MAX_DIFFERENT_SIZES)
            {
                fprintf(stderr,
                    "FixedSizeAllocator: ERROR - deallocate error ()!! size: %un",
                    (unsigned)size);
                abort();
            }
        }
        (*pp)->deallocate(pObj);
    }
};
// This is the STL compatible interface. it holds a pointer to the actual implementation of the allocator.
// Whenever this object is created from a related object (see the rebind), the reference counter for the
// allocator is incremented (decremented in d'tor).
// When the last of these is destroyed, the implementation object is also destroyed.
template<class T, size_t pool_size> class CustomPoolAllocator
{
private:
    CustomPoolAllocator &operator=(const CustomPoolAllocator &other);
public:
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T value_type;
    template<class U> struct rebind
    {
        typedef CustomPoolAllocator<U, pool_size> other;
    };
    std::shared_ptr<FixedSizeAllocator> allocImp;
    CustomPoolAllocator() :
        allocImp(new FixedSizeAllocator(pool_size))
    {
    }
    template<class Q>
    CustomPoolAllocator(const CustomPoolAllocator<Q, pool_size>& other) :
        allocImp(other.allocImp)
    {
    }
    pointer address(reference x) const
    {
        return &x;
    }
    const_pointer address(const_reference x) const
    {
        return &x;
    }
    pointer allocate(size_type n)
    {
        return (pointer)allocImp->allocate(sizeof(T), n);
    }
    void deallocate(pointer p, size_type n)
    {
        allocImp->deallocate(p, n * sizeof(T));
    }
    size_type max_size() const
    {
        return pool_size;
    }
    void construct(pointer p, const T& val)
    {
        new (p)T(val);
    }
    void destroy(pointer p)
    {
        p->~T();
    }
};
#endif // _CustomPoolAllocator_H_