Deque陷阱
程序开发过程中deque的使用频率非常高,一个是它比较方便,同时效率上也非常的高。从来也没有怀疑MS VC6自带的deque会有问题。不过今天确确实实让他害了一把。
下面是我的写的简单测试程序:
#include "stdafx.h"
#include <deque>
struct TestStruct
{
char test[1366]; //如果这个SIZE >= 1366就会crash
};
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
std::deque<TestStruct> test;
TestStruct a;
test.push_back(a);
test.erase(test.begin());
return 0;
}
从代码上来看没有任何的问题,可是程序执行到erase的时候就会出现crash。
从crash的位置来看是拷贝内存数据时候的内存地址乱了,可是自己只加了一个TestStruct,怎么也会出现内存乱了的情况。
通过对DEQUE代码的跟踪,发现MS VC6自带的DEQUE还是有一点小小的问题(还是特意这样处理的)。
当我把a压到test的时候,事实上DEQUE一次alloc了两个TestStruct,同时生成一个长度为2的MAP来记录new出来的TestStruct。
void _Buyback()
{_Tptr _P = allocator.allocate(_DEQUESIZ, (void *)0);
if (empty())
{_Mapsize = _DEQUEMAPSIZ;
size_type _N = _Mapsize / 2;
_Getmap();
_Setptr(_Map + _N, _P);
_First = iterator(_P + _DEQUESIZ / 2, _Map + _N);
_Last = _First; }
所以push_back应该是没有问题。返回的_First的数据都是记录在_Map + 1的位置上了。
那我们再来看一下erase执行的代码。
iterator erase(iterator _P)
{return (erase(_P, _P + 1)); }
iterator erase(iterator _F, iterator _L)
{size_type _N = _L - _F;
size_type _M = _F - begin();
if (_M < end() - _L)
{copy_backward(begin(), _F, _L);
for (; 0 < _N; --_N)
pop_front(); }
else
{copy(_L, end(), _F);
for (; 0 < _N; --_N)
pop_back(); }
return (_M == 0 ? begin() : begin() + _M); }
其实erase的实现也是非常的简单,主要是内存和指针的移动和删除的数据的清理。
问题的关键是出现在_P + 1上了。下面是P +1的真正实现函数:
void _Add(difference_type _N)
{difference_type _Off = _N + _Next - _First;
difference_type _Moff = (0 <= _Off)
? _Off / _DEQUESIZ
: -((_DEQUESIZ - 1 - _Off) / _DEQUESIZ);
if (_Moff == 0)
_Next += _N;
else
{_Map += _Moff;
_First = *_Map;
_Last = _First + _DEQUESIZ;
_Next = _First + (_Off - _Moff * _DEQUESIZ); }}
由于只加了一个TestStruct,所以_Next - _First为1,那么_Off =2,计算出来的_Moff为1,
由于只加了一个元素,我们只有一个_Map,那么我们执行_Map += _Moff,就导致取得的_First,_Last,_Next都指向了非法地址,最终导致程序Crash。
所以通过上面的分析,如果一个结构的大小超过1365的时候就要注意了,因为只加一个元素的时候就调用了erase函数会导致程序crash。其实大部分的情况下这种结构的大小已经有点偏大了,可能有VECTOR应该会比DEQUE取得不错的性能。