我在使用std::unordered_map::emplace()
时遇到seg错误。以下是可重复性最小的示例:
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
class WordTable {
public:
WordTable() {
total = 0;
}
~WordTable() {}
void addWord(const string word, const int incr = 1) {
cout << "begin emplace" << endl;
table.emplace(word, Node()); //this is where the seg fault occurs
cout << "emplace succeeded" << endl;
if (incr) {
table[word].incrementCount();
incrementTotal();
}
}
private:
struct Node {
public:
Node() {
count = 0;
kids = new WordTable();
}
~Node() {
delete kids;
}
int incrementCount() {
return ++count;
}
private:
int count;
WordTable* kids;
};
int incrementTotal() {
return ++total;
}
int total;
unordered_map<string, Node> table;
};
int main() {
WordTable tmp;
cout << "add word 1" << endl;
tmp.addWord("Hi");
cout << "end add word 1" << endl;
tmp.addWord("Hi");
cout << "end add word 2" << endl;
WordTable* test = new WordTable();
cout << "add word 3" << endl;
test->addWord("Hi");
cout << "end add word 3" << endl;
}
以及相应的输出:
add word 1
begin emplace
emplace succeeded
end add word 1
begin emplace
emplace succeeded
end add word 2
add word 3
begin emplace
Segmentation fault (core dumped)
seg故障发生在对addWord()
的第三次调用中对.emplace()
的调用中。
应该发生的是addWord("Hi")
向std::unordered_map<std::string, Node>
表添加映射。该映射应该将"Hi"
作为键值,并将Node()
对象作为映射值。
这里有第一个奇怪的部分:如果在第三个调用之前我只有一个对addWord()
的调用,那么就没有seg错误。这是输出:
add word 1
begin emplace
emplace succeeded
end add word 1
end add word 2
add word 3
begin emplace
emplace succeeded
end add word 3
第二个奇怪的部分是,如果我静态地分配test
,那么也没有seg错误。以下是它的输出:
add word 1
begin emplace
emplace succeeded
end add word 1
begin emplace
emplace succeeded
end add word 2
add word 3
begin emplace
emplace succeeded
end add word 3
我不知道发生了什么,也不知道为什么会发生。我只是不明白STLunordered_map::emplace()
内部怎么会出现seg故障。我能想到的唯一问题是我在addWord()
中创建Node()
的方式,但我不知道这将如何使addWord()
在前两个调用中成功,但在第三个调用中出错。
我将非常感谢任何帮助!!!
在Node
中,您在构造函数和析构函数中分配并释放WordTable *kids
,但它将具有默认的复制构造函数和运算符。这些只是复制指针本身,而不是创建一个新对象,比如:
Node(const Node &cp) // default
: count(cp.count), kids(cp.kids) // no "new"!
{}
当这些副本中的第一个被销毁时,指针被删除,剩下的是一个无效的指针,这可能会在访问时崩溃(另一种选择通常是某种形式的堆损坏)。在这种情况下,第二次访问似乎因编译器而异,GCC似乎由于制作额外的副本而在emplace
中遇到问题,MSVC直到主返回(WordTable tmp
堆栈变量)。
您可以通过跟踪新的/删除的来看到这一点
Node() {
count = 0;
kids = new WordTable();
cout << "new kids " << kids << endl;
}
~Node() {
cout << "delete kids " << kids << endl;
delete kids;
}
//GCC添加单词1开始布设新生儿0xa38c30delete children 0xa38c30//在模板内被删除,但当稍后发生"~WordTable"时(如果它达到了那个程度)将被第二次删除,即存储在WordTable实例中的那个。模板成功结束添加字1开始布设new children 0xa38c30//可以获得与0xa38c30现在"免费"相同的指针删除子项0xa38c30//然后再次由于某些GCC特定的实现细节,当值"Hi"已经存在,因此不再需要该值时,两次删除children 0xa38c30//模板成功结束添加词2添加单词3开始布设新子项0xa38cf0//再次使用相同指针SIGSEGV//这次没那么幸运,可能是因为上面的双重删除损坏了一些东西
你可以通过"删除"构造函数来防止默认副本,运算符:
Node(const Node &) = delete;
Node &operator = (const Node &) = delete;
这将把table.emplace(word, Node());
变成一个编译错误,因为这是发生复制的地方。尽管您调用了emplace
,但您向它传递了一个完整的临时对象,因此它将尝试并模板化到复制构造函数Node(const Node &)
。你想对emplace
做的是向它传递构造函数参数,这对于默认的构造函数来说有点棘手,一个简单的table.emplace(word)
无法编译:
table.emplace(std::piecewise_construct, std::make_tuple(word), std::make_tuple());
或者,如果您希望您的对象是安全可复制的,请显式地实现这些函数。
Node(const Node &cp)
: count(cp.count), kids(new WordTable()) // create a full new object this time
{
*kids = *cp.kids;
cout << "copy constructor " << kids << endl;
}
Node &operator = (const Node &cp)
{
*kids = *cp.kids;
cout << "copy operator " << kids << endl;
return *this;
}
添加单词1开始布设新生0xee8c30复制构造函数0xee8cd0//这次生成了一个新对象delete children 0xee8c30//删除了原始项,但0xee8cd0仍然有效模板成功结束添加字1开始布设新生0xee8c30复制构造函数0xee8d90删除子项0xee8d90删除子项0xee8c30模板成功结束添加词2添加单词3开始布设新生0xee8d40复制构造函数0xee8de0删除子项0xee8d40模板成功结束添加字3delete children 0xee8cd0//当main返回
WordTable
的副本很好,因为unordered_map<string, Node>
将使用刚才提供的密钥/值单独复制每个密钥/值。
另一个类似的替代方案是提供合适的移动构造函数和运算符,或者与副本一起,或者删除副本。
Node(const Node &cp) = delete;
Node &operator = (const Node &cp) = delete;
Node(Node && mv)
: count(mv.count), kids(mv.kids)
{
mv.kids = nullptr; // took kids away from mv
cout << "move constructor " << kids << endl;
}
Node &operator = (Node &&mv)
{
swap(count, mv.count);
swap(kids, mv.kids);
cout << "move operator " << kids << " from " << mv.kids << endl;
return *this;
}
添加单词1开始布设new children 0x1c4cc30//临时对象移动构造函数0x1c4cc30//最终模板值delete children 0//已将其移出,因此此处未删除任何内容模板成功结束添加字1开始布设新生0x1c4ccf0移动构造函数0x1c4ccf0delete children 0x1c4ccf0//由于重复的"嗨"而删除删除孩子0//再次移动,因此为空模板成功结束添加词2添加单词3开始布设新生0x1c4ccf0移动构造函数0x1c4ccf0删除子项0模板成功结束添加字3delete children 0x1c4cc30
请记住,无论移动对象处于何种状态(例如,此处为零count
和零kids
),其本身都必须有效。因此,如果你这样做了,你需要小心并进行适当的if (kids == nullptr)
检查。
对于std::unique_ptr
来说,这样的情况也很好,在CCD_29中,一些对象在一个唯一的地方被创建和销毁,从而省去了手动delete
的需要。它还将自动阻止默认复制,因为unique_ptr
本身不允许复制,但允许移动(注意:如果您有~Node()
,则不会自动获得移动功能)。
struct Node {
public:
Node()
: count(0)
, kids(std::make_unique<WordTable>()) // std::unique_ptr(new WordTable())
{}
int incrementCount() {
return ++count;
}
private:
int count;
std::unique_ptr<WordTable> kids;
};
如果我们使用Valgrind编译和运行,我们会看到一些提示问题的编译器警告:
g++ -std=c++2a -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Weffc++ 59351752.cpp -o 59351752
59351752.cpp:23:10: warning: ‘struct WordTable::Node’ has pointer data members [-Weffc++]
23 | struct Node {
| ^~~~
59351752.cpp:23:10: warning: but does not override ‘WordTable::Node(const WordTable::Node&)’ [-Weffc++]
59351752.cpp:23:10: warning: or ‘operator=(const WordTable::Node&)’ [-Weffc++]
和我们免费后首次使用的指示:
valgrind --leak-check=full ./59351752
==1137125== Memcheck, a memory error detector
==1137125== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1137125== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1137125== Command: ./59351752
==1137125==
add word 1
begin emplace
emplace succeeded
end add word 1
begin emplace
==1137125== Invalid read of size 8
==1137125== at 0x10B3D8: std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_begin() const (hashtable.h:384)
==1137125== by 0x10AFD1: std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::clear() (hashtable.h:2028)
==1137125== by 0x10ACCD: std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::~_Hashtable() (hashtable.h:1352)
==1137125== by 0x10A905: std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, WordTable::Node, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> > >::~unordered_map() (unordered_map.h:102)
==1137125== by 0x10A94F: WordTable::~WordTable() (59351752.cpp:11)
==1137125== by 0x10AAA3: WordTable::Node::~Node() (59351752.cpp:30)
==1137125== by 0x10A9C5: WordTable::addWord(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int) (59351752.cpp:15)
==1137125== by 0x10A3B2: main (59351752.cpp:52)
==1137125== Address 0x4d7d228 is 24 bytes inside a block of size 64 free'd
==1137125== at 0x483708B: operator delete(void*, unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1137125== by 0x10AAB0: WordTable::Node::~Node() (59351752.cpp:30)
==1137125== by 0x10CBD9: std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>::~pair() (stl_pair.h:208)
==1137125== by 0x10CC05: void __gnu_cxx::new_allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, true> >::destroy<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>*) (new_allocator.h:153)
==1137125== by 0x10C58D: void std::allocator_traits<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, true> > >::destroy<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >(std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, true> >&, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>*) (alloc_traits.h:497)
==1137125== by 0x10BC32: std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, true> > >::_M_deallocate_node(std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, true>*) (hashtable_policy.h:2102)
==1137125== by 0x10B548: std::pair<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, false, true>, bool> std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_emplace<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, WordTable::Node>(std::integral_constant<bool, true>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, WordTable::Node&&) (hashtable.h:1655)
==1137125== by 0x10B0B2: std::pair<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, false, true>, bool> std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::emplace<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, WordTable::Node>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, WordTable::Node&&) (hashtable.h:749)
==1137125== by 0x10AD2D: std::pair<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node>, false, true>, bool> std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, WordTable::Node, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, WordTable::Node> > >::emplace<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, WordTable::Node>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, WordTable::Node&&) (unordered_map.h:388)
==1137125== by 0x10A9B9: WordTable::addWord(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int) (59351752.cpp:15)
==1137125== by 0x10A3B2: main (59351752.cpp:52)
==1137125== Block was alloc'd at
==1137125== at 0x4835DEF: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1137125== by 0x10AA66: WordTable::Node::Node() (59351752.cpp:27)
==1137125== by 0x10A9A6: WordTable::addWord(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int) (59351752.cpp:15)
==1137125== by 0x10A3B2: main (59351752.cpp:52)
==1137125==
这显然是由Node
中的原始指针引起的,该指针被复制到编译器生成的复制构造函数中。
如果我们简单地将Node::kids
更改为std::unique_ptr
,并在离开main()
之前删除test
,那么我们得到了一个干净的Valgrind运行。