__gnu_cxx::hash_map死循环分析
2014-08-14 16:56
295 查看
转载自:http://blog.csdn.net/tototony/article/details/5689882
<span style="font-size:12px;">/// 一个hash_map死循环的例子: class obj { public: obj(char *_name) { strncpy(name, _name, 31); } char name[32]; /// anyothers }; hash_map<char*, obj*> list; typedef hash_map<char*, obj*>::iterator hash_iter; obj *pObj = new obj("testUnion"); if(pObj) { list.insert(pObj->name, pObj); } strncpy(pObj->name, "hahaha", 31); /// 这里就会死循环 for(hash_iter it = list.begin(); it != list.end(); ++it) { /// ... } </span><span style="font-size:12px;"><span style="color:#006600;">/// 原因,见__gnu_cxx::hash_map的源代码,一句一句分析 /// for循环执行第一句hash_iter it = list.begin(); 这里调用了函数hash_map::begin(): /// __gnu_cxx::hash_map中begin()定义如下 iterator begin() { return _M_ht.begin(); }; /// 我们看下_M_ht是什么东东,__gnu_cxx::hash_map有一个typedef typedef hashtable<pair<const _Key, _Tp>, _Key, _HashFcn, _Select1st<pair<const _Key, _Tp> >, _EqualKey, _Alloc> _Ht; _Ht _M_ht; /// 也就是说_M_ht是一个hashtable类型,那么_M_ht.begin()调用也就是hashtable::begin(): </span>iterator hashtable::begin() { for(size_type __n = 0; __n < _M_buckets.size(); ++__n) if(_M_buckets[__n]) return iterator(_M_buckets[__n], this); return end(); } </span><span style="font-size:12px;"><span style="color:#006600;">/// _M_buckets的定义在hashtable中是std::vector<_Node*, _Nodeptr_Alloc>, /// 这也就是所谓的hash表,就是一个vector,而_Node的定义是_Hashtable_node, /// 也就是一个hash的结点,也就是说vector中存放了一个指针,指向一个结点 </span>template <class _Val> struct _Hashtable_node { _Hashtable_node *_M_next; /// 解决hash碰撞用的链表 _Val _M_val; /// 真正的hash值value }; </span><span style="font-size:12px;"><span style="color:#006600;">/* 所以begin函数就是循环hash表,找到第一个存在的元素,返回一个迭代器,迭代器是_Hashtable_iterator类型,其中有两个指针,第一个_M_cur指向的是hash表vector中当前的一个元素,第二个_M_ht指向了这个hash表本身。返回这个迭代器,会得到一个_M_ht指向当前hash表,_M_cur指向第一个元素的迭代器。如果hash表中没有元素,返回一个_M_cur为空的迭代器,也就是end();,接着我们的程序,下一个执行的语句应该是it != list.end(),这个比较简单,也就是看迭代器it的_M_cur指针是不是为空,在我们的程序里一定是不为空的,所以下面要执行循环体,循环体执行完成后,会执行++it,问题的关键就在这个++it,我们看下__gnu_cxx的源码,_Hashtable_iterator::operator++(): */ </span>_Hashtable_iterator& _Hashtable_iterator::operator++() { const _Node* __old = _M_cur; _M_cur = _M_cur->_M_next; /// 先在链表中找,如果有碰撞情况,会找到 if(!_M_cur) { size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val); /// 这句代码意思是使用当前迭代器指向的hash元素的关键字值得到一个hash值 while(!_M_cur && ++__bucket < _M_ht->_M_buckets.size()) _M_cur = _M_ht->_M_buckets[__bucket]; } return *this; } </span><span style="font-size:12px;color:#006600;">/* 这段程序的意思是找到当前迭代器指向元素的下一个元素,_M_bkt_num(__old->_M_val);这个函数调用传入的是当前迭代器指向的hash元素的关键字的值,而返回的是使用参数,利用hashtable的hash函数,也就是传入的模板参数_HashFcn,得到一个hash的key值,这里就出了问题,原因是这样的,我们在之前程序中insert到hash表时,那个obj对象的关键字name是“testUnion”,我们使用的hash函数得到的key是98,而在程序中我们有一句strncpy(pObj->name, "hahaha", 31);,这样,name就是"hahaha",我们使用的hash函数得到有key是34,而在程序执行到_M_ht->_M_bkt_num(__old->_M_val),我们的name已经变成了"hahaha",这个函数返回的值是34,而__gnu_cxx::hashtable并没有检查_M_ht->_M_buckets[34]是否为空指针,程序自信的以为这个是OK的,就开始找34之后的“第二个元素”,找到__bucket为98时,“第二个元素”出现了,但98其实是第一个元素,而现在_M_ht->_M_buckets[__bucket]的指针指向的地址和++it函数调用前_M_cur指向的地址是一样的,经过很多次循环后,其实程序做了一次_M_cur = _M_cur操作,那么下一次++it也只会执行相同的操作,也就没有结束的那一天了,于是死循环就出现了 解决方法很简单,strncpy(pObj->name, "hahaha", 31);之前,先把之前在hash表中的元素删除,再改名,再插入hash表就可以了,或者让gnu改改代码?这个不可能了,呵呵 */</span>
相关文章推荐
- __gnu_cxx::hash_map死循环一例
- 记录一下 __gnu_cxx::hash_map传一个新allocator的写法
- 32bit g++编译64位整数作为key的__gnu_cxx::hash_map的编译错误问题
- 32bit g++编译64位整数作为key的__gnu_cxx::hash_map的编译错误问题
- __gnu_cxx::hash_map使用中的一些问题
- [STL] __gnu_cxx::hash_map使用中的一些问题
- 记录一下 __gnu_cxx::hash_map传一个新allocator的写法
- Map中的hash()分析
- STLport::hash_map实现分析
- Map中的hash()分析的最透彻的文章
- ES6教程之for循环和Map,Set用法分析
- java----map,set,hash的源码分析---篇1
- 全网把Map中的hash()分析的最透彻的文章,别无二家。——转载自Hollis
- hash_map分析
- vs 2003 中hash_map使用方法
- 详细解说STL hash_map系列
- [pascal]对“求1-100之间的所有素数”的三种不同循环结构算法的分析
- Linux内核2.6.14源码分析-双向循环链表代码分析
- 详细解说STL hash_map系列
- Java容器分析--Map