自定义分配器- Microsoft std::map实现对相同的元素进行两次重新分配,GCC工作得很好
custom allocator - microsoft std::map implementation deallocates the same element twice, gcc works fine
我们的想法是有一个可重用的仍然兼容的分配器,它易于使用,并且不会在软件的性能关键区域使用自由存储。
下面是一些测试代码(非常基本的测试):#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_
- g++的分段错误(在NaN上使用to_string两次时)
- 蛇在C++不会连续转两次
- 检查一个数组是否包含在另一个数组中,以相反的顺序,至少两次
- 新分配指向函数的指针是否合法?
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 在什么情况下,两个堆栈分配的结构对象的 this 点指向同一个地址?
- 我应该如何去缓解两次出现的cin?
- Realloc 两次无法在 Visual Studio 上运行
- 使用 getline(cin, var) 两次在进行字符串比较时会产生错误 (==)
- 为什么映射插入和 map.find() 的单次迭代比插入和 map.find() 的两次单独迭代慢得多
- C++析构函数调用两次,堆栈分配的复合对象
- 为什么将两个对象分配给另一个对象后,两个对象不一样?
- 如何动态分配我的数组,重新分配给两次,等等
- 阵列不能两次将相同的名称保存,当要添加新字符串时,程序应检查以查看该名称是否已经存在
- 通过调用一次新运算符大容量分配对象
- C++程序要求我输入两次,即使变量第一次被分配
- C++ 使用放置新的未定义行为构造对象两次
- 自定义分配器- Microsoft std::map实现对相同的元素进行两次重新分配,GCC工作得很好
- 重新分配变量,删除调用了两次(C++)
- 我可以使用static关键字只分配一次新内存吗