VS2017使用plalloc分配器搭配Microsoft STL std::map调试报错问题
问题描述
我在VS2017环境下尝试将plalloc分配器与std::map配合使用,调试模式下触发断言失败,以下是精简后的最小可复现代码:
#include "plalloc.hpp" #include <map> typedef unsigned char uchar; struct my_info_t { int src, dst; char type; bool operator <(const my_info_t &r) const { if ( src < r.src ) return true; if ( src > r.src ) return false; return dst < r.dst; } }; typedef plalloc<std::pair<const my_info_t, uchar> > xcache_alloc; typedef std::map<my_info_t, uchar, std::less<my_info_t>, xcache_alloc> my_cache_t; int main() { my_cache_t xc; my_info_t xi = {1, 2, 3}; xc[xi] =42; }
原始代码在<xtree>的如下位置触发中断:
#if _ITERATOR_DEBUG_LEVEL == 2 _STL_VERIFY(_Where._Getcont() == _STD addressof(_My_data), "map/set insert iterator outside range"); #endif /* _ITERATOR_DEBUG_LEVEL == 2 */
精简示例则在_Iterator_base12::_Adopt()中触发已释放指针访问错误:
void _Adopt(const _Container_base12 *_Parent) noexcept { // adopt this iterator by parent if (_Parent == nullptr) { // no future parent, just disown current parent #if _ITERATOR_DEBUG_LEVEL == 2 _Lockit _Lock(_LOCK_DEBUG); _Orphan_me(); #endif /* _ITERATOR_DEBUG_LEVEL == 2 */ } else { // have a parent, do adoption _Container_proxy *_Parent_proxy = _Parent->_Myproxy; // <== **** CRASH HERE
此处_Parent值为0xdddddddd,说明指向的内存已被释放,完整栈追踪如下:
> Project1.exe!std::_Iterator_base12::_Adopt(const std::_Container_base12 * _Parent=0xdddddddd) Line 157 C++ Project1.exe!std::_Iterator_base12::operator=(const std::_Iterator_base12 & _Right={...}) Line 135 C++ Project1.exe!std::_Iterator_base12::_Iterator_base12(const std::_Iterator_base12 & _Right={...}) Line 116 C++ Project1.exe!std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<my_info_t const ,unsigned char> > >,std::_Iterator_base12>::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<my_info_t const ,unsigned char> > >,std::_Iterator_base12>(const std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<my_info_t const ,unsigned char> > >,std::_Iterator_base12> & __that={...}) C++ Project1.exe!std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<my_info_t const ,unsigned char> > > >::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<my_info_t const ,unsigned char> > > >(const std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<my_info_t const ,unsigned char> > > > & __that={...}) C++ Project1.exe!std::map<my_info_t,unsigned char,std::less<my_info_t>,plalloc<std::pair<my_info_t const ,unsigned char> > >::_Try_emplace<my_info_t const &>(const my_info_t & _Keyval={...}) Line 232 C++ Project1.exe!std::map<my_info_t,unsigned char,std::less<my_info_t>,plalloc<std::pair<my_info_t const ,unsigned char> > >::try_emplace<>(const my_info_t & _Keyval={...}) Line 248 C++ Project1.exe!std::map<my_info_t,unsigned char,std::less<my_info_t>,plalloc<std::pair<my_info_t const ,unsigned char> > >::operator[](const my_info_t & _Keyval={...}) Line 363 C++ Project1.exe!main() Line 29 C++
Dinkumware的STL实现逻辑比较晦涩,尤其是代理分配器相关机制,很难定位问题根因,请问这个故障是出自plalloc分配器本身、我的使用方式,还是Microsoft STL的实现问题?
故障原因及解决方案
- 故障根因是plalloc分配器本身不兼容MSVC调试模式的STL实现,既不是使用方式错误,也不是MSVC STL的bug。
- MSVC默认调试模式下
_ITERATOR_DEBUG_LEVEL=2,所有STL容器都会额外分配一个_Container_proxy内部结构,用于跟踪关联迭代器、检测迭代器悬垂/越界问题。这个结构的内存需要通过对用户传入的分配器执行rebind<_Container_proxy>得到新分配器实例后分配。 - plalloc是为单类型固定大小节点设计的专用有状态分配器,核心逻辑依赖固定大小的内存块自由链表,没有正确实现分配器的rebind语义:当被rebind到其他类型(比如远小于你定义的pair大小的
_Container_proxy)时,分配的内存大小不足、或者释放逻辑错乱,导致容器的proxy结构被提前释放,后续迭代器访问时就命中了调试模式下标记已释放内存的0xdddddddd地址。
- MSVC默认调试模式下
- 可选解决方案:
- 临时规避:在项目调试配置的预处理器定义中添加
_ITERATOR_DEBUG_LEVEL=0,关闭迭代器调试功能,重新编译即可正常运行,但会丢失调试模式下的迭代器错误检测能力。 - 根因修复:修改plalloc的实现,对rebind到非原始类型的分配/释放请求,直接转发给全局
operator new/delete处理,不走plalloc自身的自由链表逻辑;或者改用已经兼容MSVC平台特性的固定大小分配器即可。
- 临时规避:在项目调试配置的预处理器定义中添加
内容的提问来源于stack exchange,提问作者Igor Skochinsky
相关产品推荐
相关产品推荐

