__gnu_cxx::hash_map死循环分析

来源:互联网 发布:淘宝卖家怎么提高销量 编辑:程序博客网 时间:2024/06/01 09:58

转载自: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有一个typedeftypedef 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>

0 0
原创粉丝点击