灵巧指针与内存回收.docx
- 文档编号:13183827
- 上传时间:2023-06-11
- 格式:DOCX
- 页数:21
- 大小:21.92KB
灵巧指针与内存回收.docx
《灵巧指针与内存回收.docx》由会员分享,可在线阅读,更多相关《灵巧指针与内存回收.docx(21页珍藏版)》请在冰点文库上搜索。
灵巧指针与内存回收
灵巧指针与内存回收
在JAVA和C#中都有垃圾回收功能,程序员在分配一段内存后可以不再理会,而由垃圾回收自动回收,从而使程序员从复杂的内存管理中解脱出来。
这是JAVA和C#的一大优点。
而C++程序员在用new分配了一段内存后,还必须用delete释放,否则将造成资源泄漏。
因此,一些C++书上经常告诫程序员:
要养成好的习惯,new与delete要成对出现,时刻记住将内存释放回系统。
但是,事情只是这么简单吗?
经常地,在使用C++的过程中,我们会遇到下面的情形:
classA
{
public:
A();
~A();
SetNextPtr(A*Ptr)
{pNext=Ptr;}
private:
A*pNext;
}
一般地,为了不引起内存泄漏,我们会在析构函数中释放pNext,象下面这样:
A:
:
~A()
{
if(pNext)
deletepNext;
pNext=NULL;
}
对于一般情况,这样就够了,但在某些情形下,这样也会出问题的,象下面这样:
A*ptrB=newA;;
A*ptrA=newA;
ptrB->SetNextPtr(ptrA);
ptrA->SetNextPtr(ptrB);
deleteptrB;
这样会出问题,因为这些指针连成了一个回环,无论从那一个点开始删除,都会造成一个指针被删除两次以上,这将使得程序抛出异常。
当然,也有一些方法可以用来解决这个问题,但是我要说明的是:
对于C++程序员来说,养成一个好的习惯并不难,难就难在有时候这样将把你带入一种逻辑的混乱当中,增加一些不必要的麻烦,有时甚至不知所措。
可是如何解决这个问题呢?
如果C++也具有垃圾回收的功能,那么,这个问题当然就迎刃而解了。
但是C++属于编译型语言,不会具备这个功能。
长期以来,我也一直在思考这个问题,想找出一种方法来使自己从这种麻烦中解脱出来。
直到最近开始学习泛型编程,看到灵巧指针的介绍以后,我灵光一闪,终于找到了办法来解决这个问题。
大家知道,灵巧指针具有一些灵巧特性,如在构造时可以自动初始化,析构时可以自动释放所指的指针。
我们就利用它的这些特性来实现我们的垃圾回收。
首先,我们要想办法来对我们用new分配的每一段内存增加引用记数,即记录下当前指向它的灵巧指针个数,当最后一个指向它的指针被释放时,我们就可以释放这段内存了。
由此,我们进行了new和delete的全局重载,并引入了CPtrManager类。
voidoperatordelete(void*p)
{
intmark=thePtrManager.GetMarkFromPtr(p);
if(mark>0)
thePtrManager.UserDelete(mark);
free(p);
}
void*operatornew(size_tsize)
{
returnthePtrManager.MallocPtr(size);
}
classCPtrManager
{
public:
intGetCount(intmark,void*p);//得到当前的引用记数
staticCPtrManager*GetPtrManager();//得到全局唯一的CPtrManager指针
voidUserDelete(intmark);//删除mark标志的指针,并对指针和标志复位
void*MallocPtr(size_tsize);//new()调用它分配内存;
BOOLAddCount(intmark,void*Ptr);//增加引用记数
BOOLRelease(intmark,void*Ptr);//减少引用记数
intGetMarkFromPtr(void*Ptr);//通过指针得到标志
CPtrManager();
virtual~CPtrManager();
private:
staticCPtrManager*p_this;//指向全局唯一的CPtrManager指针
voidAddPtr(void*Ptr);//增加一个新分配的内存
CPtrArraym_ptr;//存放分配的指针的可变数组
CUIntArraym_count;//存放指针的引用记数
void*pCurrent;//最近刚分配的指针
unsignedintm_mark;//最近刚分配的指针的标志
CUIntArraym_removed;//存放m_ptr中指针被删除后所空留的位置
};
顾名思义,CPtrManager就是用来管理指针的,对于我们用new分配的每一个指针,都存放在m_ptr[index]中,并在m_count[index]中存放它的引用记数。
同时,我们对每一个指针都增加了一个标志(mark>0,<=0为无效),这个标志同时存在于灵巧指针中(后面将看到),这是为了一种双重保险,并且在这里,这个标志就等于指针在m_ptr中的索引,这也为快速查找提供了方便。
总的思路是这样的:
当我们用new分配一个指针时,这个指针将被存入CPtrManager中,当一个灵巧指针开始拥有这个指针时,CPtrManager将负责对这个指针的引用记数加1,反之亦然,即一个灵巧指针开始释放该指针的拥有权时,CPtrManager将负责对这个指针的引用记数减1,如果引用记数为0,那么这个灵巧指针将负责对该指针delete。
下面是灵巧指针的部分介绍:
template
classauto_ptr
{
public:
auto_ptr()
{mark=0;pointee=0;}
auto_ptr(auto_ptr
auto_ptr(T*ptr);
~auto_ptr(){Remove();}
T*operator->()const;
operatorT*();
T&operator*()const;
auto_ptr
auto_ptr
private:
voidRemove();//释放所指指针的拥有权
T*pointee;//所拥有的指针
intmark;//所拥有的指针的标志
};
template
:
Remove()
{
CPtrManager*pMana=CPtrManager:
:
GetPtrManager();
if(pointee&&pMana)
{
if(pMana->Release(mark,pointee))//减少引用记数
{
if(pMana->GetCount(mark,pointee)==0)
deletepointee;//如果引用记数为0,delete指针
}
else//所拥有的指针不在CPtrManager中,有可能在栈中
{
//Userdecidetodo
}
}
pointee=NULL;//复位
mark=0;
}
template
:
auto_ptr(auto_ptr
{
pointee=rhs.pointee;
mark=rhs.mark;
CPtrManager*pMana=CPtrManager:
:
GetPtrManager();
if(pMana)
pMana->AddCount(mark,pointee);//增加引用记数
}
template
:
auto_ptr(T*ptr)
{
mark=0;
pointee=ptr;
CPtrManager*pMana=CPtrManager:
:
GetPtrManager();
if(pMana)
{
mark=pMana->GetMarkFromPtr(ptr);//得到指针的标志
if(mark>0)
pMana->AddCount(mark,pointee);//如果标志不为0,增加引用记数
}
}
template
:
operator=(auto_ptr
{
if(pointee!
=rhs.pointee)
{
Remove();//释放当前指针的拥有权
pointee=rhs.pointee;
mark=rhs.mark;
CPtrManager*pMana=CPtrManager:
:
GetPtrManager();
if(pMana)
pMana->AddCount(mark,pointee);
}
return*this;
}
template
:
operator=(T*ptr)
{
if(pointee!
=ptr)
{
Remove();
pointee=ptr;
CPtrManager*pMana=CPtrManager:
:
GetPtrManager();
if(pMana)
{
mark=pMana->GetMarkFromPtr(ptr);
if(mark>0)
pMana->AddCount(mark,pointee);
}
}
}
当到了这里时,我便以为大功告成,忍不住摸拳搽掌,很想试一试。
结果发现对于一般的情况,效果确实不错,达到了垃圾回收的效果。
如下面的应用:
auto_ptr
auto_ptr
auto_ptr
但是,很快地,我在测试前面提到的回环时,就发现了问题,我是这样测试的:
classtest
{
auto_ptr
};
auto_ptr
auto_ptr
p1->p=p2;
p2->p=p1;
当程序执行离开作用域后,这两块内存并没有象我想象的那样被释放,而是一直保留在堆中,直到程序结束。
我仔细分析造成这种现象的原因,发现了一个非常有趣的问题,我把它称之为互锁现象。
上面p1所拥有的指针被两个灵巧指针所拥有,除p1外,还有p2所拥有的test类中的灵巧指针p,p2亦然。
也就是说,这两块内存的指针的引用记数都为2。
当程序执行离开作用域后,p1,p2被析构,使它们的引用记数都为1,此后再没有灵巧指针被析构而使它们的引用记数变为0,因此它们将长期保留在堆中。
这就象两个被锁住的箱子,其中每个箱子中都装着对方的钥匙,但却无法把彼此打开,这就是互锁现象。
可是如何解决呢?
看来必须对它进行改进。
同时,我也发现上面的方法不支持多线程。
所以,我们改进后的方法不仅要解决互锁现象,而且还要支持多线程。
下面是我改进后的方法:
首先是如何发现这种互锁现象。
我们知道,互锁现象产生的根源在于拥有堆中内存的灵巧指针本身也存在于已分配的堆内存中,那么,如何发现灵巧指针是存在于堆中还是栈中就成了问题的关键。
由此,我引入了一个新的类CPtr,由它来管理用new分配的指针,而CPtrManager专门用来管理CPtr。
如下所示:
classCPtr
{
friendclassCMark;
public:
intGetPtrSize();//得到用new分配指针的内存的大小
CMutex*GetCMutex();//用于线程同步
void*GetPtr();//得到用new分配的指针
CPtr();
virtual~CPtr();
intGetCount();//得到引用记数
voidAddAutoPtr(void*autoPtr,intAutoMark);//加入一个拥有该指针的灵巧指针
BOOLDeleteAutoPtr(void*autoPtr);//释放一个灵巧指针的拥有权
voidSetPtr(void*thePtr,intSize,intmark,intcount=0);//设置一个新的指针
voidoperatordelete(void*p)
{
free(p);
}
void*operatornew(size_tsize)
{
returnmalloc(size);
}
private:
intm_mark;//指针标志
intm_count;//引用记数
void*m_ptr;//分配的指针
intm_size;//指针指向内存的大小
CPtrArrayAutoPtrArray;//存放拥有该指针的所有灵巧指针的指针数组
CUIntArraym_AutoMark;//灵巧指针标志:
0inthestack;>0=mark
CMutexmutex;//用于线程同步
};
classCPtrManager
{
public:
intGetAutoMark(void*ptr);//通过灵巧指针的指针,得到灵巧指针标志
CPtrManager();
virtual~CPtrManager();
intGetMarkFromPtr(void*Ptr);
void*MallocPtr(size_tsize);
BOOLbCanWrite();
voidDeletePtr(intmark,void*Ptr);
voidAddPtr(void*Ptr,intPtrSize);
staticCPtrManager*GetPtrManager();
CPtr*GetCPtr(void*Ptr,intmark);//通过指针和指针标志得到存放该指针的CPtr
private:
CPtrArraym_ptr;//存放CPtr的指针数组
void*pCurrent;
unsignedintm_mark;
CUIntArraym_removed;
BOOLbWrite;//在解决互锁现象的过程中,谢绝其它线程的处理
staticCPtrManager*p_this;
CMutexmutex;//用于线程同步
CMarkTablemyMarkTable;
voidRemoveLockRes();//处理互锁内存
voidCopyAllMark();//处理互锁现象前,先对所有的CPtr进行拷贝
staticUINTmyThreadProc(LPVOIDlparm);//处理互锁现象的线程函数
CWinThread*myThread;
voidBeginThread()
{myThread=AfxBeginThread(myThreadProc,this,THREAD_PRIORITY_ABOVE_NORMAL);}
};
上面的应用中加入了灵巧指针的标志,其实,这个标志就等于该灵巧指针所存在的内存的指针的标志。
例如:
我们用new分配了一个test指针,假如这个指针的标志mark=1,那么,这个test中的灵巧指针auto_ptr
如果一个灵巧指针存在于栈中,那么它的automark=0。
反之亦然,如果一个灵巧指针的automark等于一个指针的mark,那么,该灵巧指针必存在于这个指针所指的内存中。
可是,如何得到这个标志呢,请看下面这个函数的实现:
intCPtrManager:
:
GetAutoMark(void*ptr)
{
CSingleLocksingleLock(&mutex);
singleLock.Lock();//线程同步
intsize=m_ptr.GetSize();
for(inti=1;i { CPtr*theCPtr=(CPtr*)m_ptr[i]; if(theCPtr) { intptrFirst=(int)theCPtr->GetPtr();//得到内存的首指针 intptrEnd=ptrFirst+theCPtr->GetPtrSize();//得到内存的尾指针 intp=(int)ptr;//灵巧指针的指针 if(p>=ptrFirst&&p<=ptrEnd)//比较灵巧指针的指针是否在首尾之间 returni; } } return0; } 这个函数的原理就在于: 如果一个灵巧指针存在于一块内存中,那么该灵巧指针的指针必在这块内存的首尾指针之间。 解决了灵巧指针的位置问题,下一步就是要找出所有被互锁的内存的指针。 这个好实现,只要所有拥有这个指针的灵巧指针的automark>0,那么,这块内存就可能被互锁了(注意只是可能),接着看下面的实现: classCMark { friendclassCMarkTable; public: CMark(){} virtual~CMark(){} voidoperatordelete(void*p) { free(p); } void*operatornew(size_tsize) { returnmalloc(size); } voidCopyFromCPtr(CPtr*theCPtr);//从CPtr中拷贝相关信息 BOOLbIsNoneInStack();//判断拥有该指针的所有灵巧指针是否都不在栈中 voidRelease();//解除该指针的互锁 private: intm_mark;//指针的标志 CPtrArrayautoptrArray;//拥有该指针的所有灵巧指针的指针数组 CUIntArrayautomarkArray;//拥有该指针的所有灵巧指针的标志 }; classCMarkTable { public: CMarkTable(){Init();} virtual~CMarkTable(){} voidAddCMark(CMark*theCMark); BOOLFindMark(intmark); voidInit(); voidDoLockMark();//处理互锁问题 private: CPtrArrayCMarkArray;//暂存从CPtrManager中拷贝过来的指针信息的CMark指针数组 CPtrArrayCLockMarkArray;//存放互锁的内存 voidGetLockMark();//得到所有可能被互锁的内存的CMark,结果存放于CLockMarkArray BOOLFindLockMark(intmark);//判断一个灵巧指针是否存在于CLockMarkArray所包含的指针中 voidRemoveUnlockMark();//去除假的互锁内存 voidRemoveGroup(intautomark);//对互相有联系的相互死锁的内存进行分组 }; 这里又引入了两个类: CMark和CMarkTable,这是为了在处理互锁问题之前,对CPtrManager中的CPtr进行快速拷贝,以防止影响其它线程的正常运行。 其实,这里的CMark与CPtr没有什么区别,它只是简单地从CPtr中拷贝信息,也就是说,它等同于CPtr。 为了处理互锁问题,先要把可能被互锁的内存指针找出来,看下面函数的实现: voidCMarkTable: : GetLockMark() { CLockMarkArray.SetSize(0); intsize=CMarkArray.GetSize(); for(inti=0;i { CMark*theMark=(CMark*)CMarkArray[i]; if(theMark) { if(theMark->bIsNoneInStack()) CLockMarkArray.SetAtGrow(i,theMark); } } } 把这些内存找出来之后,就需要把那些假锁的内存找出来,什么是假锁呢? 看下面的例子: 对于指针ptrA,如果它的灵巧指针autoA存在于指针ptrB中,而ptrB的灵巧指针autoB又存在于ptrA中,那么ptrA和ptrB是真锁,但是如果ptrB的灵巧指针autoB存在于指针ptrC中,而ptrC的灵巧指针autoC存在于栈中,那么,ptrA和ptrB属于假锁。 怎么找出假锁的内存呢? 看下面函数的实现: voidCMarkTable: : RemoveUnlockMark() { CUIntArrayUnlockMarkArray; BOOLbNoneRemoveed; do { bNoneRemoveed=TRUE; UnlockMarkArray.SetSize(0); intsize=CLockMarkArray.GetSize(); for(inti=0;i { CMark*theMark=(CMark*)CLockMarkArray[i]; if(theMark) { intsize1=(theMark->automarkArray).GetSize(); for(intj=0;j { intmark=(theMark->automarkArray)[j]; if(! FindLockMark(mark))//判断灵巧指针是否存在于CLockMarkArray所包含的指针中 { UnlockMarkArray.InsertAt(0,i);//recordtoremove bNoneRemoveed=FALSE; break; } } } else {UnlockMarkArray.InsertAt(0,i); bNoneRemoveed=FALSE; } } intsize2=UnlockMarkArray.GetSize(); for(intk=0;k
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 灵巧 指针 内存 回收
![提示](https://static.bingdoc.com/images/bang_tan.gif)