调用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
相关产品推荐
相关产品推荐

