为啥下面的代码会崩溃?
Posted
技术标签:
【中文标题】为啥下面的代码会崩溃?【英文标题】:Why does the following code crash?为什么下面的代码会崩溃? 【发布时间】:2012-12-09 17:39:00 【问题描述】:这只是创建一些列表元素,然后在开始通过反向迭代接近它时删除一个元素。这是一个实际问题的复制品,即在反向遍历元素时删除元素的代码。
#include <list>
int main()
std::list< int > lst;
for ( int c = 33; c--; )
lst.push_back( 0 );
int count = 0;
for ( std::list< int >::reverse_iterator i = lst.rbegin(), e = lst.rend();
i != e; )
switch( count++ )
case 32:
case 33:
++i;
i = std::list< int >::reverse_iterator( lst.erase( i.base() ) );
break;
default:
++i;
return 0;
运行时会崩溃:
*** glibc detected *** ./a.out: double free or corruption (out): 0x00007fff7f98c230 ***
当使用 valgrind 运行时,它会说:
==11113== Invalid free() / delete / delete[] / realloc()
==11113== at 0x4C279DC: operator delete(void*) (vg_replace_malloc.c:457)
==11113== by 0x40104D: __gnu_cxx::new_allocator<std::_List_node<int> >::deallocate(std::_List_node<int>*, unsigned long) (in /tmp/a.out)
==11113== by 0x400F47: std::_List_base<int, std::allocator<int> >::_M_put_node(std::_List_node<int>*) (in /tmp/a.out)
==11113== by 0x400E50: std::list<int, std::allocator<int> >::_M_erase(std::_List_iterator<int>) (in /tmp/a.out)
==11113== by 0x400BB6: std::list<int, std::allocator<int> >::erase(std::_List_iterator<int>) (in /tmp/a.out)
==11113== by 0x40095A: main (in /tmp/a.out)
编译器:
$ g++ --version
g++ (Debian 4.7.1-7) 4.7.1
拱门:
$ uname -a
Linux hostname 3.2.0-2-amd64 #1 SMP Mon Apr 30 05:20:23 UTC 2012 x86_64 GNU/Linux
你认为这是一个错误,还是我在这里做错了什么?
附言如果您删除 case 33
(这绝不应该发生),这将变成无限循环而不是崩溃。
【问题讨论】:
count
中是否存在 off by 1 错误? (应该是switch(++count)
?)
否 - 循环的退出条件是i != e
,它不依赖于计数。
那是 - 它被关闭了,但它不应该导致任何问题。但是,由于内存损坏,迭代实际上继续超过 32。我已经编辑了这个问题,以便更清楚地说明这一点。
您在执行lst.erase(i.base())
时更新i
,但您可能还需要更新e
或简单地使用i != lst.rend()
作为您的for
终止条件(手头没有编译器验证)。
@ChrisSchmich:但为什么e
会失效?它指向第一个元素之前。
【参考方案1】:
好的,所以我拿出笔和纸,现在我认为 与您的 e
迭代器无效有关。请记住,反向迭代器包含一个普通的迭代器,它指向容器中的下一个元素,即它的基迭代器。也就是说,当您拥有指向最后一个元素的 rbegin()
迭代器时,它的内部迭代器将指向最后一个元素。同样,当 rend()
迭代器指向开始前的迭代器(反向迭代器可以指向的虚构元素)时,它的内部迭代器指向第一个元素。
所以你的列表看起来像这样(BTB = 开始之前,PTE = 结束之后):
BTB | 0 | 0 | ... | 0 | 0 | PTE
^ : ^ :
|----' |----'
e i
虚线表示基础迭代器指向的位置。
现在,在第一次迭代中,您指向最后一个元素(反向第一个),count
为 0,因为您执行后缀递增。因此,当开关与 32
匹配时,您将指向列表中的第一个元素(反向第 33 个)。
好的,现在我们处于这种状态:
BTB | 0 | 0 | ... | 0 | 0 | PTE
^ ^ :
|----|---'
e i
然后执行以下代码:
++i;
i = std::list< int >::reverse_iterator( lst.erase( i.base() ) );
第一行让我们处于这种状态:
BTB | 0 | 0 | ... | 0 | 0 | PTE
^ :
|----'
i
e
然后你擦除基迭代器指向的元素并设置你的反向迭代器,以便它的基现在指向被擦除元素之后的元素。现在我们有:
BTB | 0 | ... | 0 | 0 | PTE
^ ^ :
|---|----'
e i
不过,现在e
已失效。它的 base 不再指向列表的第一个元素,它指向一个无效元素。
现在,您的循环应该停止,因为 i
已经结束,但它不会。下次再继续,count
为33
,先做i++
:
BTB | 0 | ... | 0 | 0 | PTE
^ :
|---'
i
e
然后试图擦除基地。哦亲爱的!基地没有指向一个有效的元素,我们得到了崩溃。事实上,我认为一旦你迭代得太远,你就已经遇到了未定义的行为。
解决方案
修复它的方法是每次迭代时获取rend()
:
for ( std::list< int >::reverse_iterator i = lst.rbegin();
i != lst.rend(); )
或者,当您删除元素时更新e
:
++i;
i = std::list< int >::reverse_iterator( lst.erase( i.base() ) );
e = lst.rend();
现在,我之前的回答是交换增量和擦除,这很有效,但为什么呢?好吧,让我们回到重要的点(为了在接下来的几个步骤中清晰起见,我添加了另一个元素):
BTB | 0 | 0 | 0 | ... | 0 | 0 | PTE
^ ^ :
|----|---'
e i
所以现在我们先擦除基础,给我们这个:
BTB | 0 | 0 | ... | 0 | 0 | PTE
^ ^ :
|----|-------'
e i
然后我们增加i
:
BTB | 0 | 0 | ... | 0 | 0 | PTE
^ :
|----'
i
e
然后i == e
结束循环。因此,虽然这 确实 有效,但它并没有达到你想要的效果。它只删除第二个元素。
【讨论】:
我使用了以下广受好评的答案:***.com/questions/8621426/… 嗯,该拿出笔和纸了。 我们查看的是相同的代码吗?我的意思是那里接受的答案。它显然和我做的一样。 @dragonroot 我改变了答案。 @dragonroot 确实如此。reverse_iterator
是一个迭代器适配器。该标准说:“反向迭代器与其对应的迭代器i
之间的基本关系由身份建立:&*(reverse_iterator(i)) == &*(i - 1).
”换句话说,取消引用反向迭代器会在它所适应的迭代器之前为您提供一个元素。
【参考方案2】:
错误是e
失效,你应该直接比较lst.rend()
。为什么会失效?好,我们来看看rend()
(§23.2.1.9
)的定义:reverse_iterator(begin())
。
所以rend()
的构造依赖于指向第一个元素的begin()
迭代器,您实际上可以用case 32
删除它。因此,由于该操作使begin()
无效,它很可能也使rend()
无效,具体取决于它的实现方式,这显然发生在这个版本的libstdc++
中。
case 33
让它崩溃也是有道理的,这一次迭代器将指向不再在列表中的东西。删除它当然会无限循环,因为e
无效并且您的停止条件不会命中。
【讨论】:
§23.2.1.9 - 这是来自 ISO/IEC 14882 的吗?我在这里没有看到这样的段落。 这是 n3242 'draft' 版本【参考方案3】:当你删除元素时,e
无效!缓动后你必须更新e
:
i = std::list< int >::reverse_iterator( lst.erase( i.base() ) );
e = lst.rend(); // update e
【讨论】:
从列表中擦除只会使您擦除的元素的迭代器无效。我不确定这会对反向迭代器产生什么影响。我想rend
的基迭代器是列表的第一个元素,将失效。
@sftrabbit:感觉确实如此。不过,这对我来说没有多大意义。
因为++i
在擦除前,i == e
所以那个节点会失效。【参考方案4】:
这应该工作 -
#include <list>
#include <iostream>
int main()
std::list< int > list;
for ( int c = 33; c--; )
list.push_back( 0 );
std::list<int>::reverse_iterator it = list.rbegin();
int count = 0;
while( it != list.rend() )
switch( count++ )
case 32:
case 33:
std::cout<<*it<<std::endl;
it = std::list< int >::reverse_iterator( list.erase((++it).base()));
std::cout<<list.size()<<std::endl;
break;
default:
++it;
return 0;
【讨论】:
以上是关于为啥下面的代码会崩溃?的主要内容,如果未能解决你的问题,请参考以下文章
为啥这个简单的 NSWindow 创建代码会在 ARC 下关闭时触发自动释放池崩溃?
为啥 PyQt 在没有信息的情况下会崩溃? (退出代码 0xC0000409)