C ++池分配程序仅在控制台关闭时崩溃

时间:2019-02-24 21:10:53

标签: c++ memory-management visual-studio-2012

我正在看这个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中运行此代码时,它似乎可以正常工作,直到关闭控制台为止,此时出现访问冲突错误。我尝试手动在分配器上调用析构函数,它似乎可以正常工作,但是我必须在某个地方犯了一个错误。我得到的错误是:

enter image description here

有人能发现我在犯错吗?

编辑1:

根据进一步调查,即使没有main中的新Node行,它仍然会崩溃。似乎与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;
}

它导致相同的错误!情节变厚。我以为编写自定义分配器是新手,分配器代码就是问题所在。仍然不确定为什么我的较小代码确实会发生此错误...

2 个答案:

答案 0 :(得分:1)

写一个答案,因为我不能对此问题发表评论。

我无法在上一个代码中发现任何明显的错误。您确定要编译正确的文件,而不是未保存的旧版本吗?

您可以尝试删除该行

while (true){}

然后让程序正常结束。

此外,您可以尝试以调试模式运行代码,单步执行说明以查找引起问题的代码。

答案 1 :(得分:1)

我可以发现该池分配器存在一些问题。

  1. PoolAllocator拥有资源,但是既没有特殊的副本构造函数,也没有赋值。您很可能应该声明它们已删除。并提供移动构造器和移动分配。尽管在此特定示例中不是一个因素,但是它可以保护您免于过分地按值返回分配器。

  2. 函数alloc_pool()在分配新块之前重置next_pos。如果被operator new抛出异常,则会使池处于不一致状态。

  3. 同样,pools.push_back()中的异常将看到新的块泄漏。我相信std::vector<std::vector<std::byte>>会做的正确,现代矢量可以移动。但是,如果绝对要使用原始指针向量,则应在pools中保留额外的空间,然后分配新的块,然后再调用push_back并修改状态。

  4. PoolAllocator的构造函数可能没有充分的理由抛出。 由于allocate()方法无论如何都必须调用alloc_pool(),为什么还要在构造函数中调用它呢?您只需将所有分配工作都留给allocate()就可以拥有一个简单的noexcept构造函数。

  5. PoolAllocator::allocate()PoolAllocator::~PoolAllocator()不对称。前者返回没有初始化对象的原始内存,而后者假定每个分配的插槽中都有一个正确构造的对象。这种假设是危险的,而且非常脆弱。例如,想象一下T::T()会抛出。

  6. 似乎getSize()和getCount()总是返回相同的值。是故意的吗?

  7. 析构函数将删除 first 池中的next_pos对象,其他每个池中的pools[0]T_per_page对象。但是它应该删除 last 池中的next_pos对象。

  8. 如果从池的析构函数调用的T:~T()曾经试图从该池中分配另一个对象,那么您将遇到许多很棒的错误。这种情况看似很奇怪,但是从技术上讲,它可能会发生。析构函数最好将池的当前状态交换为局部变量并对其进行处理。重复,如有必要。

  9. main()中的无限循环可能破坏全局对象的破坏。编译器可能很聪明,可以找出return无法访问并完全跳过破坏部分。

  10. pool_size可以是静态成员。