一 陷阱现象描述
最近在项目中需要实现一个超时数据自动删除功能,采用了deque这个数据结构使用它的头删尾插功能。在实现删除功能时,程序出现了crash。
程序在iOS平台上未出现异常现象,但是在Android平台偶尔会出现crash现象。通过ndk-stack 定位堆栈,发现是crash在下面这个地方。
仔细分析这里的item是一个deque结构 m_naks的迭代器(it)的引用,只是对变量执行自增,并没有其它复杂逻辑。那么为什么会crash在这个地方呢?再有一种可能那就是item是个非法的迭代器自增在写内存的时候出现crash。m_naks只存在push_back()、pop_front()、erase ()这些操作,前面两种不会出现非法的内存访问现象,只有这个erase (it ++)可能出现问题。
二 现象原因分析
经过查看相关资料发现对于vector、deque这类序列式容器其内部数据结构是一个数组,在使用erase ()函数时需要注意一下几点。
vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同)。当把超过capacity()-size()个元素插入vector中时,内存会重新分配,所有的迭代器都将失效;否则,指向当前元素以后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素以后的任何元素的迭代器都将失效。
增加任何元素都将使deque的迭代器失效。在deque的中间删除元素将使迭代器失效。在deque的头或尾删除元素时,只有指向该元素的迭代器失效。
deque内存结构示意图
内存地址均为假设仅用于表示连续的内存地址空间,如果要删除it指向的这片空间deque的内存结构将发生变化,所以在删除时执行it++,将导致it指向下一个内存空间,但是erase()执行完毕之后,deque的内存结构已经发生变化。此时it将指向一个未知的空间,必定会导致程序出现crash现象。
三 原因总结
1 所以上述迭代器的正确删除方式如下:
调用erase函数之后,将它的返回值赋给it。
2 对于关联容器map、set、multimap、multiset、list,这些数据结构在删除当前迭代器时,不会使当前迭代器失效(因为这些迭代器底层实现上,并非使用连续内存空间,而是使用了红黑树、链)。
3 对于vector、deque 这2种数据结构的迭代器,删除操作将导致当前迭代器失效,注意合理使用。