C++ 池分配器程序仅在控制台关闭时崩溃
C++ pool allocator program crashing only on console close
我正在查看这个 pool allocator 实现。我实际上对它进行了一些修改,我的完整代码是:
template <class T, size_t T_per_page = 200>
class PoolAllocator
{
private:
const size_t pool_size = T_per_page * sizeof(T);
std::vector<T *> pools;
size_t count;
size_t next_pos;
void alloc_pool() {
next_pos = 0;
void *temp = operator new(pool_size);
pools.push_back(static_cast<T *>(temp));
}
public:
PoolAllocator() {
count = 0;
alloc_pool();
}
void* allocate() {
if (next_pos == T_per_page)
alloc_pool();
void* ret = pools.back() + next_pos;
++next_pos;
++count;
return ret;
}
size_t getSize() const
{
return T_per_page * (pools.size() - 1) + next_pos;
}
size_t getCount() const
{
return count;
}
size_t getCapacity() const
{
return T_per_page * pools.size();
}
T* get(size_t index) const
{
if (index >= getCount()) { return NULL; }
size_t poolIndex = index / T_per_page;
return pools[poolIndex] + (index % T_per_page);
}
~PoolAllocator() {
std::cout << "POOL ALLOCATOR DESTRUCTOR CALLED" << std::endl;
while (!pools.empty()) {
T *p = pools.back();
size_t start = T_per_page;
if (pools.size() == 1){
start = next_pos;
}
std::cout << "start: " << start << std::endl;
for (size_t pos = start; pos > 0; --pos)
{
std::cout << "pos: " << pos << std::endl;
p[pos - 1].~T();
}
operator delete(static_cast<void *>(p));
pools.pop_back();
}
}
};
template<class T>
PoolAllocator<T>& getAllocator()
{
static PoolAllocator<T> allocator;
return allocator;
}
class Node
{
private:
int id;
std::vector<float> vertices;
public:
Node() : id(42)
{
std::cout << "Node constructor called" << std::endl;
}
~Node(){ std::cout << "Node destructor called" << std::endl; }
void* operator new(size_t size)
{
std::cout << "Node operator new called" << std::endl;
return getAllocator<Node>().allocate();
}
void operator delete(void*)
{
std::cout << "Node operator delete called" << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Node* n1 = new Node();
Node* n2 = new Node();
Node* n3 = new Node();
Node* n4 = new Node();
std::cout << "Count: " << getAllocator<Node>().getCount() << " size: " << getAllocator<Node>().getSize() << " capacity: " << getAllocator<Node>().getCapacity() << std::endl;
while (true){}
return 0;
}
当我 运行 visual studio 中的这段代码时,它似乎可以正常工作,直到我关闭控制台,此时我收到访问冲突错误。我试过在分配器上手动调用析构函数,它似乎工作正常,但我一定是在某个地方犯了错误。我得到的错误是:
谁能发现我哪里犯了错误?
编辑 1:
经过进一步调查,即使在 main 中没有新的节点行,它仍然会崩溃。似乎与 getAllocator() 方法以及如何调用析构函数有关?或者分配器是静态的?
编辑 2:
实际上我不认为它与我的分配器有任何关系!如果我尝试代码:
class Node2
{
private:
int x;
public:
Node2():x(42){std::cout << "Node2 constructor called" << std::endl;};
Node2(const Node2& other){ std::cout << "Node2 copy constructor called" << std::endl; };
~Node2(){ std::cout << "Node2 destructor called" << std::endl; };
};
Node2& Test(){
static Node2 myIndex;
return myIndex;
}
int _tmain(int argc, _TCHAR* argv[])
{
Test();
while (true){}
return 0;
}
结果是一样的错误!情节变厚了。我假设是编写自定义分配器的新手,分配器代码就是问题所在。仍然不确定为什么我的较小代码会发生此错误...
正在写一个答案,因为我不能对这个问题发表评论。
我在最后的代码中没有发现任何明显的错误。您确定您正在编译正确的文件而不是旧的未保存版本之类的吗?
你可以试试去掉那行
while (true){}
并让程序正常结束。
此外,您可以尝试 运行 您的代码处于调试模式,单步执行指令以找出导致问题的指令。
我可以发现该池分配器存在一些问题。
PoolAllocator
拥有资源,但既没有特殊的复制构造函数也没有赋值。您很可能应该声明它们已删除。并提供移动构造函数和移动赋值。虽然不是这个特定示例中的一个因素,但它可以保护您免于心不在焉 return 按值分配分配器。
函数 alloc_pool()
在分配新块之前重置 next_pos
。 operator new
抛出的异常会使池处于不一致状态。
同样,pools.push_back()
中的异常会导致新块泄漏。我相信 std::vector<std::vector<std::byte>>
会做得恰到好处,现代矢量是可移动的。但是如果你绝对想使用原始指针向量,你应该在 pools
中保留额外的 space,然后分配新的块,然后才调用 push_back
并修改状态。
PoolAllocator 的构造函数可能会无故抛出。
由于 allocate()
方法无论如何都必须调用 alloc_pool()
,为什么要在构造函数中调用它?您只需将所有分配工作留给 allocate()
.
即可拥有一个简单的 noexcept 构造函数
PoolAllocator::allocate()
和 PoolAllocator::~PoolAllocator()
不对称。前者 return 没有初始化对象的原始内存,而后者假设每个分配的槽中都有一个正确构造的对象。这种假设是危险的,而且非常脆弱。例如,假设 T::T()
抛出。
getSize() 和 getCount() 似乎总是 return 相同的值。是故意的吗?
析构函数将删除第一个池中的next_pos
个对象,pools[0]
,以及每隔一个池中的T_per_page
个对象水池。但它应该删除 last 池中的 next_pos
个对象。
如果从池的析构函数中调用的 T:~T()
曾经试图从同一个池中分配另一个对象,那么您将遇到奇妙的错误。这种情况可能看起来很奇怪,但从技术上讲,它是可能发生的。析构函数最好将池的当前状态交换为局部变量并对其进行处理。如有必要,重复。
main() 中的无限循环可能破坏全局对象的破坏。编译器可能足够聪明,可以判断出 return
是不可访问的,并完全跳过破坏部分。
pool_size
可以是静态成员。
我正在查看这个 pool allocator 实现。我实际上对它进行了一些修改,我的完整代码是:
template <class T, size_t T_per_page = 200>
class PoolAllocator
{
private:
const size_t pool_size = T_per_page * sizeof(T);
std::vector<T *> pools;
size_t count;
size_t next_pos;
void alloc_pool() {
next_pos = 0;
void *temp = operator new(pool_size);
pools.push_back(static_cast<T *>(temp));
}
public:
PoolAllocator() {
count = 0;
alloc_pool();
}
void* allocate() {
if (next_pos == T_per_page)
alloc_pool();
void* ret = pools.back() + next_pos;
++next_pos;
++count;
return ret;
}
size_t getSize() const
{
return T_per_page * (pools.size() - 1) + next_pos;
}
size_t getCount() const
{
return count;
}
size_t getCapacity() const
{
return T_per_page * pools.size();
}
T* get(size_t index) const
{
if (index >= getCount()) { return NULL; }
size_t poolIndex = index / T_per_page;
return pools[poolIndex] + (index % T_per_page);
}
~PoolAllocator() {
std::cout << "POOL ALLOCATOR DESTRUCTOR CALLED" << std::endl;
while (!pools.empty()) {
T *p = pools.back();
size_t start = T_per_page;
if (pools.size() == 1){
start = next_pos;
}
std::cout << "start: " << start << std::endl;
for (size_t pos = start; pos > 0; --pos)
{
std::cout << "pos: " << pos << std::endl;
p[pos - 1].~T();
}
operator delete(static_cast<void *>(p));
pools.pop_back();
}
}
};
template<class T>
PoolAllocator<T>& getAllocator()
{
static PoolAllocator<T> allocator;
return allocator;
}
class Node
{
private:
int id;
std::vector<float> vertices;
public:
Node() : id(42)
{
std::cout << "Node constructor called" << std::endl;
}
~Node(){ std::cout << "Node destructor called" << std::endl; }
void* operator new(size_t size)
{
std::cout << "Node operator new called" << std::endl;
return getAllocator<Node>().allocate();
}
void operator delete(void*)
{
std::cout << "Node operator delete called" << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Node* n1 = new Node();
Node* n2 = new Node();
Node* n3 = new Node();
Node* n4 = new Node();
std::cout << "Count: " << getAllocator<Node>().getCount() << " size: " << getAllocator<Node>().getSize() << " capacity: " << getAllocator<Node>().getCapacity() << std::endl;
while (true){}
return 0;
}
当我 运行 visual studio 中的这段代码时,它似乎可以正常工作,直到我关闭控制台,此时我收到访问冲突错误。我试过在分配器上手动调用析构函数,它似乎工作正常,但我一定是在某个地方犯了错误。我得到的错误是:
谁能发现我哪里犯了错误?
编辑 1:
经过进一步调查,即使在 main 中没有新的节点行,它仍然会崩溃。似乎与 getAllocator() 方法以及如何调用析构函数有关?或者分配器是静态的?
编辑 2:
实际上我不认为它与我的分配器有任何关系!如果我尝试代码:
class Node2
{
private:
int x;
public:
Node2():x(42){std::cout << "Node2 constructor called" << std::endl;};
Node2(const Node2& other){ std::cout << "Node2 copy constructor called" << std::endl; };
~Node2(){ std::cout << "Node2 destructor called" << std::endl; };
};
Node2& Test(){
static Node2 myIndex;
return myIndex;
}
int _tmain(int argc, _TCHAR* argv[])
{
Test();
while (true){}
return 0;
}
结果是一样的错误!情节变厚了。我假设是编写自定义分配器的新手,分配器代码就是问题所在。仍然不确定为什么我的较小代码会发生此错误...
正在写一个答案,因为我不能对这个问题发表评论。
我在最后的代码中没有发现任何明显的错误。您确定您正在编译正确的文件而不是旧的未保存版本之类的吗?
你可以试试去掉那行
while (true){}
并让程序正常结束。
此外,您可以尝试 运行 您的代码处于调试模式,单步执行指令以找出导致问题的指令。
我可以发现该池分配器存在一些问题。
PoolAllocator
拥有资源,但既没有特殊的复制构造函数也没有赋值。您很可能应该声明它们已删除。并提供移动构造函数和移动赋值。虽然不是这个特定示例中的一个因素,但它可以保护您免于心不在焉 return 按值分配分配器。函数
alloc_pool()
在分配新块之前重置next_pos
。operator new
抛出的异常会使池处于不一致状态。同样,
pools.push_back()
中的异常会导致新块泄漏。我相信std::vector<std::vector<std::byte>>
会做得恰到好处,现代矢量是可移动的。但是如果你绝对想使用原始指针向量,你应该在pools
中保留额外的 space,然后分配新的块,然后才调用push_back
并修改状态。PoolAllocator 的构造函数可能会无故抛出。 由于
allocate()
方法无论如何都必须调用alloc_pool()
,为什么要在构造函数中调用它?您只需将所有分配工作留给allocate()
. 即可拥有一个简单的 noexcept 构造函数
PoolAllocator::allocate()
和PoolAllocator::~PoolAllocator()
不对称。前者 return 没有初始化对象的原始内存,而后者假设每个分配的槽中都有一个正确构造的对象。这种假设是危险的,而且非常脆弱。例如,假设T::T()
抛出。getSize() 和 getCount() 似乎总是 return 相同的值。是故意的吗?
析构函数将删除第一个池中的
next_pos
个对象,pools[0]
,以及每隔一个池中的T_per_page
个对象水池。但它应该删除 last 池中的next_pos
个对象。如果从池的析构函数中调用的
T:~T()
曾经试图从同一个池中分配另一个对象,那么您将遇到奇妙的错误。这种情况可能看起来很奇怪,但从技术上讲,它是可能发生的。析构函数最好将池的当前状态交换为局部变量并对其进行处理。如有必要,重复。main() 中的无限循环可能破坏全局对象的破坏。编译器可能足够聪明,可以判断出
return
是不可访问的,并完全跳过破坏部分。pool_size
可以是静态成员。