You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++二叉树迭代器使用std::allocator触发double free错误排查

问题现象

实现升序遍历的二叉树迭代器时,程序运行抛出Free() double free detected when trying to free memory using std::allocator错误,移除迭代器析构中的内存释放逻辑后程序可正常运行,崩溃发生在打印最后一个节点值之前。
附复现代码:

#include <memory>
#include <map>
#include <iostream>
#include <cstddef>

template <class T>
    class BinaryNode
    {
    public:
        T       data;
        BinaryNode* left;
        BinaryNode* right;
        BinaryNode* parent;
        BinaryNode(T data) : data(data), left(NULL), right(NULL), parent(NULL)
        {
        }
        BinaryNode(T data, BinaryNode* parent) : data(data), left(NULL), right(NULL), parent(parent)
        {
        }
    };
    template <class T, class Compare, class Node=BinaryNode<T>, class Alloc=std::allocator<Node> >
    class BinaryTreeIterator
    {
    public:
        typedef T    value_type;
        typedef T& reference;
        typedef T* pointer;

        typedef Alloc               allocator_type;
        typedef Compare             key_compare;
        BinaryTreeIterator(Node* curr, Node* first, Node* last, size_t sz) : curr(curr), first(first), last(last), sz(sz), comp(key_compare()), alloc(allocator_type())
        {
            this->end = this->alloc.allocate(1);
            this->alloc.construct(this->end, Node(std::make_pair(sz, typename value_type::second_type())));
        }
        ~BinaryTreeIterator()
        {
            this->alloc.destroy(this->end);
            this->alloc.deallocate(this->end, 1);
        }
        reference operator*() const
        {
            if (this->curr == NULL)
                return (this->end->data);
            else
                return (this->curr->data);
        }
        pointer operator->() const
        {
            if (this->curr == NULL)
                return (&this->end->data);
            else
                return (&this->curr->data);
        }
        BinaryTreeIterator& operator++()
        {
            Node* tmp = this->curr;
            if (this->curr == NULL)
            {
                this->curr = this->first;
            }
            else if (tmp->right == NULL)
            {
                tmp = tmp->parent;
                while (tmp != NULL && this->comp(tmp->data.first, this->curr->data.first))
                    tmp = tmp->parent;
                this->curr = tmp;
            }
            else
            {
                tmp = tmp->right;
                while (tmp->left != NULL)
                {
                    tmp = tmp->left;
                }
                this->curr = tmp;
            }
            return (*this);
        }
        BinaryTreeIterator operator++(int)
        {
            BinaryTreeIterator tmp = *this;
            ++*this;
            return (tmp);
        }
        Node* curr;
        Node* first;
        Node* last;
        Node* end;
        size_t sz;
        key_compare comp;
        allocator_type alloc;
    };

typedef std::pair<const int, int> pair;
typedef BinaryNode<pair> Node;
int main()
{
    std::allocator<Node> alloc;
    Node* root = alloc.allocate(1);
    alloc.construct(root, Node(std::make_pair(5, 5)));
    root->right = alloc.allocate(1);
    alloc.construct(root->right, Node(std::make_pair(6, 5), root));
    root->left = alloc.allocate(1);
    alloc.construct(root->left, Node(std::make_pair(4, 5), root));
    BinaryTreeIterator<pair, std::less<int> > bst(root->left, root->left, root->right, 5);
    std::cout << bst->first << std::endl;
    bst++;
    std::cout << bst->first << std::endl;
    bst++;
    std::cout << bst->first << std::endl;
}
问题根因

崩溃是两个问题叠加导致的:

  • 浅拷贝引发double free
    后自增运算符operator++(int)返回值是迭代器对象类型,返回时会调用编译器默认生成的拷贝构造函数,该函数仅做浅拷贝:临时对象tmp和原迭代器bst的end指针会指向同一块堆内存。临时对象在bst++;语句执行结束后就会析构,释放end指向的内存,后续临时对象再次析构、或者原迭代器bst析构时,会重复释放同一块内存,直接触发double free错误。这个崩溃点刚好出现在第二次bst++执行后、第三次打印之前,和观察到的崩溃时机完全吻合。
  • 中序遍历后继逻辑错误
    右子树为空时向上回溯父节点的判断条件写反了:现有逻辑是当父节点key小于当前节点key时继续向上跳,会直接跳过正确的后继节点走到NULL。正确逻辑应该是一直向上找,直到找到第一个key大于当前节点的父节点才停止。
修复方案
  • 修正迭代器的内存所有权设计:迭代器不应该持有end节点的所有权,参考STL实现规范,end节点应该由二叉树容器本身持有,迭代器仅保存节点指针,不需要在迭代器析构函数中释放end节点内存,从根源避免浅拷贝导致的重复释放问题。如果暂时不实现容器层,可以先给迭代器实现深拷贝逻辑、或者用引用计数管理end节点内存,临时验证功能。
  • 修正父节点回溯的判断条件,把循环判断从this->comp(tmp->data.first, this->curr->data.first)改为tmp != NULL && !this->comp(this->curr->data.first, tmp->data.first),即只要父节点key不大于当前节点key,就继续向上回溯。

内容的提问来源于stack exchange,提问作者Sami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:27:29