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

调用unordered_map的reserve触发Access violation问题求助

问题描述

使用搭配自定义Bump Allocator(线性分配连续内存块)的unordered_map时,调用reserve方法后多次执行Begin→Flush循环,会在std list的以下代码行触发Access Violation异常:

_List_unchecked_const_iterator& operator++() noexcept {
    _Ptr = _Ptr->_Next;
    return *this;
}

分配器仍有充足剩余内存,可排除内存不足问题。可复现的完整代码如下:

#include <stdlib.h> 
#include <unordered_map>
#include <vector>
#include <list>
#include <memory>

#define SEQ(type, val) sizeof(type) * val

struct ImpAllocator {
    virtual void* Allocate(size_t pSize) = 0;
    virtual void Free(void* pPtr) = 0;
};

struct SysAllocator : public ImpAllocator {
    void* Allocate(size_t pSize) override {
        return malloc(pSize);
    }

    void Free(void* pPtr) override {
        free(pPtr);
    }

};

template <class T>
class StdAllocatorWrapper {
public:
    std::shared_ptr<ImpAllocator> mInternalAllocator;

    using value_type = T;

    StdAllocatorWrapper() = default;
    StdAllocatorWrapper(std::shared_ptr<ImpAllocator> pInternalAllocator) :
        mInternalAllocator(pInternalAllocator)
    {}
    ~StdAllocatorWrapper() = default;
    StdAllocatorWrapper(const StdAllocatorWrapper<T>& pOther) = default;

    template<class U>
    StdAllocatorWrapper(const StdAllocatorWrapper<U>& pOther) {
        this->mInternalAllocator = pOther.mInternalAllocator;
    }

    value_type* allocate(size_t pNumberOfObjects) {
        return reinterpret_cast<T*>(mInternalAllocator->Allocate(SEQ(T, pNumberOfObjects)));
    }

    void deallocate(value_type* pPointer, size_t pNumberOfObjects) {
        mInternalAllocator->Free(pPointer);
    }
};

template <class T, class U>
bool operator==(StdAllocatorWrapper<T> const& pL, StdAllocatorWrapper<U> const& pR) noexcept {
    return pL.mInternalAllocator == pR.mInternalAllocator;
}

template <class T, class U>
bool operator!=(StdAllocatorWrapper<T> const& pL, StdAllocatorWrapper<U> const& pR) noexcept {
    return !(pL == pR);
}

template<typename T> using AllocWrapper = StdAllocatorWrapper<T>;
template<typename T, typename K> using Pair = std::pair<const T, K>;
template<typename T, typename K> using PairAllocWrapper = StdAllocatorWrapper<Pair<T, K>>;
template<typename T> using AllocatedVector = std::vector<T, AllocWrapper<T>>;
template<typename T> using AllocatedList = std::list<T, AllocWrapper<T>>;
template<typename T, typename K> using AllocatedUnorderedMap = std::unordered_map<T, K, std::hash<T>, std::equal_to<T>, PairAllocWrapper<T, K>>;

typedef unsigned char* MemBlock;

class BumpAllocator : public ImpAllocator {
private:
    std::shared_ptr<ImpAllocator> mInternalAllocator;
    size_t mSize;
    MemBlock mBlock;
    MemBlock mStart;;
    size_t mCurrent;

public:

    BumpAllocator(size_t pSize, std::shared_ptr<ImpAllocator> pInternalAllocator) :
        mInternalAllocator(pInternalAllocator),
        mSize(pSize),
        mCurrent(0) {
        mBlock = reinterpret_cast<MemBlock>(mInternalAllocator->Allocate(pSize));
        mStart = mBlock;
    }

    ~BumpAllocator() {
        mInternalAllocator->Free(mBlock);
    }

    void* Allocate(size_t pSize) override {
        printf("\n bump allocator wrapper requested: %d", pSize);
        if (mCurrent + pSize > mSize) {
            return nullptr;
        }
        MemBlock _return = mBlock + mCurrent;
        mCurrent += pSize;
        return _return;
    }

    void Free(void* pFre) override {

    }

    void Reset() {
        mCurrent = 0;
    }
};

struct Animation {

};

struct Texture {

};

struct TextureArrayIndex {
    //TexturePointer mTexture;
    unsigned int mIndex;
    std::shared_ptr<Texture> mTexture;
};

struct RenderOrder {
    float mDeltaTime;
    std::string mAnimationName;
    std::shared_ptr<Animation> mAnim;
};

using Textures = AllocatedUnorderedMap<int, TextureArrayIndex>;
using TexturesAllocWrapper = PairAllocWrapper<int, TextureArrayIndex>;
using RenderOrdersVector = AllocatedVector<RenderOrder>;
using RenderOrdersAllocWrapper = AllocWrapper<RenderOrder>;
using RenderBucket = AllocatedUnorderedMap<unsigned int, RenderOrdersVector>;
using RenderBuckets = AllocatedUnorderedMap<std::shared_ptr<Animation>, RenderBucket>;
using RenderBucketAllocWrapper = PairAllocWrapper<unsigned int, RenderOrdersVector>;
using RenderBucketsAllocWrapper = PairAllocWrapper<std::shared_ptr<Animation>, RenderBucket>;


struct Renderer {

    std::shared_ptr<BumpAllocator> mInternalAllocator;
    Textures mTextureArrayIndexMap;
    RenderBuckets mAnimationRenderBuckets;

    Renderer(std::shared_ptr<ImpAllocator> pAllocator) :
        mInternalAllocator(std::make_shared<BumpAllocator>(60000, pAllocator)),
        mTextureArrayIndexMap(Textures(TexturesAllocWrapper(mInternalAllocator))),
        mAnimationRenderBuckets(RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator))) 
    {}

    void Begin() {
        mTextureArrayIndexMap = Textures(TexturesAllocWrapper(mInternalAllocator));
        mTextureArrayIndexMap.reserve(2);
        mAnimationRenderBuckets = RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator));
        mAnimationRenderBuckets.reserve(1000);
    }

    void Render() {

    }

    void Flush() {
        mInternalAllocator->Reset();
    }

};

int main(int argc, char* argv[]) {

    Renderer _renderer(std::make_shared<SysAllocator>());

    for (int i = 0; i < 1000; i++) {
        _renderer.Begin();
        _renderer.Flush();
    }
}

异常会在第三次Begin→Flush循环时必然触发。

问题原因分析
  • Bump Allocator的Free空实现与容器析构逻辑冲突:BumpAllocator的Free方法是空实现,但标准库容器(如unordered_map)析构时会调用分配器的deallocate方法,这导致容器内部节点的内存不会被任何处理。当Flush调用Reset复用内存后,新容器的内存会覆盖旧容器的内存区域,而旧容器的内部结构(如链表节点指针)仍指向这些已被覆盖的内存,后续析构或操作容器时就会访问非法指针。
  • 容器赋值操作导致的内存重叠与迭代器失效:Begin方法中直接对成员容器赋值,旧容器的内存来自BumpAllocator,重置分配器后新容器的内存会与旧容器内存重叠。旧容器在赋值后析构时,遍历内部链表结构(如unordered_map的桶链表)会访问到已被覆盖的_Next指针,触发Access Violation。
  • 第三次循环触发异常的原因:前两次循环中内存重叠程度较低,旧容器的析构操作未触及完全无效的指针;第三次循环时内存被多次复用,旧容器的内部链表结构已被完全覆盖,析构时遍历链表就会访问到无效指针。
解决方法

方法1:修改Begin方法,先销毁旧容器再创建新容器

避免直接赋值导致的内存重叠问题,确保旧容器的内部对象被正确析构后再创建新容器:

void Begin() {
    // 手动析构旧容器
    mTextureArrayIndexMap.~Textures();
    // 原地构造新容器
    new (&mTextureArrayIndexMap) Textures(TexturesAllocWrapper(mInternalAllocator));
    mTextureArrayIndexMap.reserve(2);

    mAnimationRenderBuckets.~RenderBuckets();
    new (&mAnimationRenderBuckets) RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator));
    mAnimationRenderBuckets.reserve(1000);
}

这种方式会彻底销毁旧容器的内部结构,避免后续访问无效指针。

方法2:在Flush前清空所有容器

BumpAllocator适合一次性分配、批量释放的场景,重置分配器前需确保所有使用该分配器的容器内部对象已析构:

void Flush() {
    // 清空容器,触发内部对象析构
    mTextureArrayIndexMap.clear();
    mAnimationRenderBuckets.clear();
    // 重置分配器内存
    mInternalAllocator->Reset();
}

同时保持Begin方法的赋值逻辑即可,清空容器后旧容器的内部结构不再指向有效内存,避免析构时的非法访问。

方法3:优化容器赋值逻辑

可以在Begin中先将容器置空,再赋值新容器,确保旧容器的内部结构被清理:

void Begin() {
    mTextureArrayIndexMap.clear();
    mTextureArrayIndexMap = Textures(TexturesAllocWrapper(mInternalAllocator));
    mTextureArrayIndexMap.reserve(2);

    mAnimationRenderBuckets.clear();
    mAnimationRenderBuckets = RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator));
    mAnimationRenderBuckets.reserve(1000);
}

这种方式相对简单,能避免大部分内存重叠导致的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 08:10:27