今天调试代码,定位一个compiler 死循环问题。 最终定位出来的原因给作者在下面这篇文章中总结的基本一样。需要指出的是这是程序员及其容易犯的一个错误。忘记将std::list的成员函数erase 返回给iter 本身。这样导致iter 成为类似野指针(虽然iter 不是指针,但是本质上是包裹起来的指针),行为将变得undefined。 如果在某些平台上,比如Linux,操作系统没有立即回收内存,有可能该问题本隐藏起来。但是在其他立即回收内存(或硬删除而不是移动到缓存)的平台上,则会造成死循环。
下面是问题代码,及修复代码:
//这里假设上下文已经设置好了myList (有了元素)
// str 是作为函数入参数传进来, const char * 类型
原代码 (错误使用方法3,相对于下面链接中的两种错误方法) :
std::list<const char *>::iterator myList ;
for(std::list<const char *>::iterator I = myList.begin(), E = myList.end(); I != E; ++I) {
if (!strcmp(*I, str)) {
_myList.erase(I);
--I;
}
}
修复代码 (正确使用方法3,相对于下面链接中的两种正确方法):
std::list<const char *>::iterator myList ;
for(std::list<const char *>::iterator I = myList.begin(), E = myList.end(); I != E; ++I) {
if (!strcmp(*I, str)) {
I = _myList.erase(I);
--I;
}
}
// comment : erase 返回指向删除元素下一个位置元素的指针,先-- 在++,仍正确指向
// 删除元素的下一个位置,可以正常工作。
另外两种错误及正确方法,见下面的链接。 个人比较喜欢正确方法三,虽然需要强制--,再++,但是毕竟避免了把++操作从for () 语句中移动到句外的情况。
总而言之,凡是让程序员记着干某些事情的设计都不是好设计。因为他们总是忘记。 比如这里的让他们记得把erase()返回值付给iter 本身,以及记得new 了以后一定要delete。正如用auto_ptr 或Java 自动垃圾回收方法可以解决到处泛滥的内存泄露问题。或许我们也应该设计一种方法,让erase() 接口不用强求程序员记着赋值给iter。 强制给出void,并让iter 自动指向下个元素也不是一个好方法。程序员使用不当的话,有可能导致跳过元素。笔者没有什么好的idea。如果各位路过的大神,有什么好的想法,欢迎留言讨论。
http://blog.csdn.net/lanbing510/article/details/8796048
下面材料整理自Internet&著作。
STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector 、deque);另一类是以不连续的节点形式存储的容器(如:list、set、map)。在使用erase方法来删除元素时,需要注意一些问题。
在使用 list、set 或 map遍历删除某些元素时可以这样使用:
或
下面是两个错误的使用方法:
或
正确使用方法1:通过erase方法的返回值来获取下一个元素的位置
正确使用方法2:在调用erase方法之前先使用 “++”来获取下一个元素的位置
错误使用方法1:在调用erase方法之后使用“++”来获取下一个元素的位置,由于在调用erase方法以后,该元素的位置已经被删除,如果在根据这个旧的位置来获取下一个位置,则会出现异常。
错误使用方法2:同上。
No comments:
Post a Comment