Consider the code below:
#include
#include
#include
using std::string;
struct string_hashfunc {
size_t operator()(const string *a) const
{
size_t hash = 0;
if(a == NULL) return 0;
for (string::const_iterator it = a->begin(); it != a->end(); it++) {
hash = hash * (*it);
}
}
};
typedef __gnu_cxx::hash_map StringHash;
int main(void) {
StringHash sh;
string *a = new string("earth");
string *b = new string("sky");
sh[a] = b;
for (StringHash::iterator it = sh.begin(); it != sh.end(); it++) {
delete it->first;
delete it->second;
}
}
It could be compiled and run on CentOS 5 (gcc-4.1.2), but will core dump at runtime.
g++ test.cpp -o test -g2 -O0
./test
Segmentation fault
The gdb stack shows the breakpoint is in string_hashfunc::operator():
(gdb) bt
#0 0x00007ffff7b7c1e3 in std::basic_string, std::allocator >::end() const () from /usr/lib64/libstdc++.so.6
#1 0x0000000000400fd0 in string_hashfunc::operator() (this=0x7fffffffe861, a=0x606620) at test.cpp:12
......
Let’s see the source code of “ext/hash_map” in /usr/include/c++/4.1.2/ext/hashtable.h:
template
_Hashtable_iterator<_Val, _Key, _HF, _ExK, _EqK, _All>&
_Hashtable_iterator<_Val, _Key, _HF, _ExK, _EqK, _All>::
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);
while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size())
_M_cur = _M_ht->_M_buckets[__bucket];
}
return *this;
}
And in the implementation of _M_bkt_num():
size_type
_M_bkt_num_key(const key_type& __key) const
{ return _M_bkt_num_key(__key, _M_buckets.size()); }
size_type
_M_bkt_num(const value_type& __obj) const
{ return _M_bkt_num_key(_M_get_key(__obj)); }
size_type
_M_bkt_num_key(const key_type& __key, size_t __n) const
{ return _M_hash(__key) % __n; }
It use _M_hash() to compute the bucket number of the key, and the _M_hash() is actually string_hashfunc::operator(). The reason is clear now: the iterator want to increase, so it call operator++() –> _M_bkt_num() –> _M_bkt_num_key() –> _M_hash() –> string_hashfunc::operator() and it can’t fetch the key because it has been freed in “delete it->first”.
How about new g++ and new c++ library? Let’s try to write the same program on CentOS 7 (gcc-4.8.5) and change “ext/hash_map” to “unordered_map” (for c++ 11 standard):
#include
#include
#include
using std::string;
struct string_hashfunc {
size_t operator()(const string *a) const
{
size_t hash = 0;
if(a == NULL) return 0;
for (string::const_iterator it = a->begin(); it != a->end(); it++) {
hash = hash * (*it);
}
}
};
typedef std::unordered_map StringHash;
int main(void) {
StringHash sh;
string *a = new string("earth");
string *b = new string("sky");
sh[a] = b;
for (StringHash::iterator it = sh.begin(); it != sh.end(); it++) {
delete it->first;
delete it->second;
}
}
Then build it:
g++ test.cpp -o test -g2 -O0 -std=c++11
./test
Everything goes normal because the new implementation of c++ library use “_M_nxt” to point to the next hash node instead of using hash function (could see it in /usr/include/c++/4.8.5/bits/hashtable_policy.h).