问题
CArray是一个很有用的C++类,他给我们编程带来诸多方便和高效。但是如果你比较仔细的话,会发现其实他有一个严重的错误,请看下面一段代码你可以清晰看到错误:
CArray< int,int&> my_carray;
int some_number = 1;
my_carray.Add(some_number);
for(int i=0; i<=10; i++) {
my_carray.Add(my_carray[0]);
}
TRACE("\nIndex\tValue");
for(int j=0; j<=10; j++) {
TRACE("\n%d\t%d", j, my_carray[j]);
}
The TRACE output is:
Index Value
0 1
1 -572662307
2 1
3 1
4 1
5 -572662307
6 1
7 1
8 1
9 -572662307
10 1
跟踪到Afxtempl.h
Afxtempl.h的一些小代码可以帮助你清晰的看出问题的所在,下面我们开始来看这个Add函数:
AFX_INLINE int CArray< TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
{
int nIndex = m_nSize;
SetAtGrow(nIndex, newElement);
return nIndex;
}
其实并没有什么奇怪发生在这个Add函数中,他就是调用了SetAtGrow:
template< class TYPE, class ARG_TYPE>
void CArray< TYPE, ARG_TYPE>::SetAtGrow(int nIndex, ARG_TYPE newElement)
{
ASSERT_VALID(this);
ASSERT(nIndex >= 0);
if (nIndex >= m_nSize)
SetSize(nIndex+1, -1);
m_pData[nIndex] = newElement;
}
注意这个SetSize 被调用时的条件表达式。下面我们再来看看这个SetSize函数: (这个函数比较大,只要仔细看加粗字体的代码就行了)
template< class TYPE, class ARG_TYPE>
void CArray< TYPE, ARG_TYPE>::SetSize(int nNewSize, int nGrowBy)
{
ASSERT_VALID(this);
ASSERT(nNewSize >= 0);
if (nGrowBy != -1)
m_nGrowBy = nGrowBy; // set new size
if (nNewSize == 0)
{
// shrink to nothing
if (m_pData != NULL)
{
DestructElements< TYPE>(m_pData, m_nSize);
delete[] (BYTE*)m_pData;
m_pData = NULL;
}
m_nSize = m_nMaxSize = 0;
}
else if (m_pData == NULL)
{
// create one with exact size
#ifdef SIZE_T_MAX
ASSERT(nNewSize <= SIZE_T_MAX/sizeof(TYPE)); // no overflow
#endif
m_pData = (TYPE*) new BYTE[nNewSize * sizeof(TYPE)];
ConstructElements< TYPE>(m_pData, nNewSize);
m_nSize = m_nMaxSize = nNewSize;
}
else if (nNewSize <= m_nMaxSize)
{
// it fits
if (nNewSize > m_nSize)
{
// initialize the new elements
ConstructElements< TYPE>(&m_pData[m_nSize], nNewSize-m_nSize);
}
else if (m_nSize > nNewSize)
{
// destroy the old elements
DestructElements< TYPE>(&m_pData[nNewSize], m_nSize-nNewSize);
}
m_nSize = nNewSize;
}
else
{
// otherwise, grow array
int nGrowBy = m_nGrowBy;
if (nGrowBy == 0)
{
// heuristically determine growth when nGrowBy == 0
// (this avoids heap fragmentation in many situations)
nGrowBy = m_nSize / 8;
nGrowBy = (nGrowBy < 4) ? 4 : ((nGrowBy > 1024) ? 1024 : nGrowBy);
}
int nNewMax;
if (nNewSize < m_nMaxSize + nGrowBy)
nNewMax = m_nMaxSize + nGrowBy; // granularity
else
nNewMax = nNewSize; // no slush
ASSERT(nNewMax >= m_nMaxSize); // no wrap around
#ifdef SIZE_T_MAX
ASSERT(nNewMax <= SIZE_T_MAX/sizeof(TYPE)); // no overflow
#endif
TYPE* pNewData = (TYPE*) new BYTE[nNewMax * sizeof(TYPE)];
// copy new data from old
memcpy(pNewData, m_pData, m_nSize * sizeof(TYPE));
// construct remaining elements
ASSERT(nNewSize > m_nSize);
ConstructElements< TYPE>(&pNewData[m_nSize], nNewSize-m_nSize);
// get rid of old stuff (note: no destructors called)
delete[] (BYTE*)m_pData;
m_pData = pNewData;
m_nSize = nNewSize;
m_nMaxSize = nNewMax;
}
}
我们可以看到这里发生了什么,在函数SetSize里m_pData被删除并接指向一块新的内存空间,而且一旦他返回SetAtGrow函数以后,执行代码m_pData[nIndex] = newElement 时,newElement引用的对象其实已经被释放了,里面的值已经不是原先的内容!
可能出现这种问题的情形
这个问题一般出现在同时满足以下三种情况时:
1) CArray模板类的第二个参数是引用时;
2) 当你调用CArray的函数而且把一个已经存在的CArray元素作为调用参数时;
3) 调用Add,SetAtGrow,InsertAt引起的内存分配操作在函数SetSize里;
解决方法
我们还是有好多方法来避免和消除这个问题的:
1) 不要使用第二个参数为引用类型的CArrays对象。这个方法对于小数据类型的比较好用,比如int,但是对于大数据结构的类型就适合了。(举个例子CArray< int,int&> 会发生这个问题,而CArray< int,int> 这样就ok了)
2) 建立一个临时对象作为Add的参数对象。
3) 修改Afxtempl.h,在删除没m_pData之前完成赋值。
4) std::vector
转自:http://huangdingjun.blog.163.com/blog/static/3110639200852351330579/

本文揭示了CArray模板类在特定条件下出现的一个严重错误,当使用引用类型并进行内存重新分配时,可能导致数据不一致的问题。文章详细分析了问题产生的原因,并提供了四种解决方案。
2万+

被折叠的 条评论
为什么被折叠?



