C++ unordered_map emplace() 函数抛出段错误,我不知道为啥
Posted
技术标签:
【中文标题】C++ unordered_map emplace() 函数抛出段错误,我不知道为啥【英文标题】:C++ unordered_map emplace() function throwing seg fault and I have no idea whyC++ unordered_map emplace() 函数抛出段错误,我不知道为什么 【发布时间】:2019-12-16 06:50:46 【问题描述】:我在使用std::unordered_map::emplace()
时遇到了段错误。这是最小的可重现示例:
#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)
在对.emplace()
的第三次调用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
,那么也没有段错误。输出如下:
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
我不知道发生了什么,也不知道为什么会发生。我只是不明白 STL unordered_map::emplace()
内部如何发生段错误。我能想到的唯一问题是我在addWord()
中创建Node()
的方式,但我看不出这将如何使addWord()
在前两个调用中成功,但在第三个调用中出现段错误。
我将非常感谢任何帮助!!!
【问题讨论】:
您需要向我们展示正确的minimal reproducible example,我们无法帮助您调试那些短行(尤其是因为错误很可能在其他地方)。也请花一些时间阅读how to ask good questions,以及this question checklist。 "我不知道发生了什么"" - 至少你可以看到导致问题的代码。把你自己放在我们的位置上。我们不知道是什么WordTable 真的看起来像,Node 真的看起来像什么。我们不是读心术的人。这需要一个正确的minimal reproducible example,它属于in your question。我的水晶球告诉我Node
在建造或破坏期间是邪恶的,没有好处(未初始化的table
成员盲目地delete
's),但那是一个摇摆(wild-arse-guess),我的水晶球是如今,错误多于正确。
感谢两位的反馈!我现在将努力获得一个最小的可重现示例。
WordTable *kids;
是查看std::unique_ptr
(unique_ptr<WordTable>
) 的好地方。至于崩溃,它是双重删除,通常如果你写了一个析构函数,你想编写或阻止复制构造函数/操作符,副本与原始指针相同,因此删除两次.
@manissss 我更完整地解释了编译器现在正在做什么以及如何进行复制/移动而不会使容器崩溃。
【参考方案1】:
在Node
中,您在构造函数和析构函数中分配和释放WordTable *kids
,但它会有默认 复制构造函数和运算符。
这些只会复制指针本身,而不是创建新对象,例如:
Node(const Node &cp) // default
: count(cp.count), kids(cp.kids) // no "new"!
当这些副本中的第一个被破坏时,指针被删除,而其他副本的指针无效,这可能会在访问时崩溃(替代方案通常是某种形式的堆损坏)。在这种情况下,第二次访问似乎因编译器而异,GCC 似乎在emplace
中遇到问题,因为制作了额外的副本,MSVC 直到它转到~WordTable()
返回(WordTable tmp
堆栈变量)。
您可以通过跟踪新/删除来看到这一点:
Node()
count = 0;
kids = new WordTable();
cout << "new kids " << kids << endl;
~Node()
cout << "delete kids " << kids << endl;
delete kids;
// 海合会
添加单词 1
开始就位
新孩子 0xa38c30
delete kids 0xa38c30 // 在 emplace 中被删除,但是当 `~WordTable` 稍后发生时(如果到了那么远)将被第二次删除,即存储在 WordTable 实例中的那个。
安顿成功
结尾加词 1
开始就位
new kids 0xa38c30 // 可以获得与 0xa38c30 现在“免费”相同的指针
删除孩子 0xa38c30 // 并再次删除
delete kids 0xa38c30 // 这次是两次,由于某些 GCC 特定的实现细节,当值“Hi”已经存在,因此不再需要该值时
安顿成功
结尾加词 2
添加单词 3
开始就位
new kids 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 kids 0xee8c30 // 删除原来的但 0xee8cd0 仍然有效
安顿成功
结尾加词 1
开始就位
新孩子 0xee8c30
复制构造函数 0xee8d90
删除孩子 0xee8d90
删除孩子 0xee8c30
安顿成功
结尾加词 2
添加单词 3
开始就位
新孩子 0xee8d40
复制构造函数 0xee8de0
删除孩子 0xee8d40
安顿成功
结尾加词 3
delete kids 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 kids 0x1c4cc30 // 临时对象
移动构造函数 0x1c4cc30 // 最终的 emplace 值
delete kids 0 // 将其移出,因此这里没有删除任何内容
安顿成功
结尾加词 1
开始就位
新孩子 0x1c4ccf0
移动构造函数 0x1c4ccf0
delete kids 0x1c4ccf0 // 由于重复的“Hi”而删除
delete kids 0 // 它又被移动了,所以是空的
安顿成功
结尾加词 2
添加单词 3
开始就位
新孩子 0x1c4ccf0
移动构造函数 0x1c4ccf0
删除孩子 0
安顿成功
结尾加词 3
删除孩子 0x1c4cc30
请记住,无论您将移动对象置于何种状态(例如,此处为零 count
和空 kids
)本身都必须是有效的。因此,您需要小心并有适当的if (kids == nullptr)
检查您是否这样做。
这样的案例对于std::unique_ptr
来说也是一个不错的案例,其中一些对象在一个唯一的地方被创建和销毁,从而节省了手动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;
;
【讨论】:
非常感谢您的回答!如果理解正确,您是说 seg 错误是由于孩子指针的双重删除引起的未定义行为?【参考方案2】:如果我们使用 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 运行。
【讨论】:
以上是关于C++ unordered_map emplace() 函数抛出段错误,我不知道为啥的主要内容,如果未能解决你的问题,请参考以下文章
在C++里,emplace_back可以完全取代push_back吗?
C++ std::set emplace 返回值 first second