这只是创建一些列表元素,然后通过反向迭代删除一个元素,接近它。它是代码的实际问题的复制品,它在反向遍历它们时删除元素。
#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
你认为这是一个错误,还是我在这里做错了什么?
P.S。如果删除case 33
(这应该永远不会发生),则会变为无限循环而不是崩溃。
答案 0 :(得分:11)
好的,所以我拿出笔和纸,现在我认为 与无效的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
已失效。它的基础不再指向列表的第一个元素,它指向一个无效的元素。
现在,你的循环应该停止,因为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
我们结束循环。因此,当 工作时,它不会做你想要的。它只删除第二个元素。
答案 1 :(得分:4)
错误是e
失效,您应该直接与lst.rend()
进行比较。为什么它会失效?好吧,让我们看一下rend()
(§23.2.1.9
)的定义:reverse_iterator(begin())
。
因此构造rend()
取决于begin()
迭代器,它指向第一个元素,您实际使用case 32
删除它。因此,由于该操作会使begin()
无效,因此它很可能会使rend()
无效,具体取决于它的实现方式,这在libstdc++
的此版本中显然会发生。
case 33
使它崩溃也是有道理的,这次迭代器将指向列表中根本不再存在的东西。删除它当然会无限循环,因为e
无效且你的停止条件不会被击中。
答案 2 :(得分:1)
删除元素时,e
无效!缓和后您必须更新e
:
i = std::list< int >::reverse_iterator( lst.erase( i.base() ) );
e = lst.rend(); // update e
答案 3 :(得分:0)
这应该有效 -
#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;}