对new和delete操作符的一点想法
在链表形式组成的线性表结构当进行插入以及追加记录时需要new操作符,而当对象销毁时调用的析构函数以及初始化链表函数中用到了delete操作符.但是大多数编译器实现这两个操作符时其结果却不能尽如人意,有些编译器则没有实现真正的delete功能,而只是简单的丢弃指针.这样就会形成内存碎片的大量增多,由其是链表中的节点很多,而且经常需要增减节点时更是如此.这只是其中的一点,而实现两个操作的时间也是比较长的.为了解决这两个问题,我们预先引入了"栈"这种数据结构.

在这里我们要改写Link类,给它加上一个静态的局部指针,用于指向栈顶,这一点与汇编语言的esp指针一样.由于类的静态数据成员有一个特征,那就是它的值可以被任何对象应用,即所有对象的该成员的值是相同的.
然后我们再在Link中重载delete和new操作符.当然这一步我们也可以用一般的成员函数实现,但是这样做的话LList类中的很多使用new和delete的函数都要重写.而重载两个运算符则对LList类的成员函数没有任何影响.下面给出这个类的代码,它是在Link.h文件的基础上修改的,所以我们将它命名为Link2.h.
Link2.h文件:
///////////////////////////////////////////////////////////////////////////
//定义一个数据节点的实现.
///////////////////////////////////////////////////////////////////////////
template <class type>
class Link
{
public:
type data;
Link<type> *next; //两个数据成员.
Link(const type&,Link<type> * =NULL);
Link(Link<type> * =NULL); //两个构造函数,用于简化对成员的初始化.
~Link(); //析构函数,真正用系统级的delete操作释放内存.
void* operator new(size_t);
void operator delete(void*); //在Link类中,重载new和delete运算符.
private:
static Link<type> *freelist; //静态成员,所的对象共享同一个成员.
};
///////////////////////////////////////////////////////////////////////////
//两个构造函数以及一个析构函数的实现.
///////////////////////////////////////////////////////////////////////////
template <class type>
Link<type>::Link(const type &val,Link<type> *p)
{
data=val;
next=p;
}
template <class type>
Link<type>::Link(Link<type> *p)
{
next=p;
}
///////////////////////////////////////////////////////////////////////////
//Link类中,在析构函数中,定义系统级的delete操作符,来释放内存空间,因为LList类
//的析构函数只是调用Link类的delete重载函数来释放Link类的对象,而重载函数只是
//将释放的节点的内存保存入缓冲区中,所以在Link类中要定义一个析构函数实现真正
//的释放空间.而我们只用释放我们定义的缓冲区的空间就完成了LList类和Link类中
//定义的任何Link类的对象.
///////////////////////////////////////////////////////////////////////////
template <class type>
Link<type>::~Link()
{
Link<type> *temp;
while(freelist)
{
temp=freelist;
freelist=freelist->next;
::delete temp;
}
}
///////////////////////////////////////////////////////////////////////////
//对私有静态成员进行初始化.
///////////////////////////////////////////////////////////////////////////
template <class type>
Link<type>* Link<type>::freelist=NULL; //可以直接在类外面初始化静态成员,注意格式.
///////////////////////////////////////////////////////////////////////////
//new和delete运算符的重载函数,用于自定义一个缓冲区,然后new和delete操作在该
//缓冲区中操作,当该缓冲区为空时才真正执行系统级的new和delete操作,这样不仅不
//会造成空间的浪费,而且还会大大缩小内存分配和释放的时间.其实该缓冲区为一个叫
//做栈的数据结构,该结构在后面分析.
///////////////////////////////////////////////////////////////////////////
template <class type>
void* Link<type>::operator new(size_t)
{
if(freelist==NULL)
return ::new Link<type>; //如果缓冲区为空时,才调用系统级的new返回空间.
Link<type> *temp=freelist; //否则返回缓冲区最顶端的空间.
freelist=freelist->next; //缓冲区指针释放一个节点空间.
return temp; //返回得到的空间.
}
template <class type>
void Link<type>::operator delete(void *p)
{
((Link<type>*)p)->next=freelist; //将删除的节点保存入缓冲区的顶端.注意要加括号.
freelist=((Link<type>*)p); //freelist重新指向新的缓冲区顶端.
return;
}
从上面的代码中可以看出,它重载了new和delete操作符,而且添加了一个析构函数.那么这样一来,当在LList类中的head,current,tail这三个Link类的对象指针调用new和delete操作符时实际上是调用了Link类的重载的new和delete函数.上面的实现实际上是对被删除的节点的内存的回收,delete实际上没有回收内存,而是把这个内存保存入以freelist为指针的栈中.然后下一次要调用new进行分配内存时先检查freelist是不是空,如果空时才真正调用系统的::new来分配内存,如果不为空,那么将以删除的节点的空间分配给新的节点.这样当链表要进行多次的添加删除操作时就会大大提高运行速度.而当Link类的对象销毁时才真正调用系统的::delete来实现回收内存.
注意,在两个重载函数以及LList类的成员中用Link类的对象使用系统的new和delete时,必须在它们的前面加上域操作符::,否则只是调用了Link类的重载函数,从Link的构造函数以及LList类的Clear成员函数中可以看出这一点.那么我们可以用上面的文件简单替换掉LList项目中的Link.h文件(当然要先改名),也完全可以编译并运行.这说明了重载运算符的好处,而不用修改功能实现的LList类.
当然重载不是唯一的方法,作为替代方法,我们可以把重载函数改成一般的成员函数,当用到分配内存时就可以调用相应的成员函数得到空间,还有一种方法是不改变Link.h文件,而是直接在LList.cpp文件中,声明一个全局的Link指针,然后再定义两个全局函数,这样一来,当我们需要new以及delete操作符时调用相应的全局函数,而指针作为栈顶地址.但是这两种方法的代价是需要改变LList类中的很多成员函数,同时增加了复杂度,所以上面的第一种方法最好.
而在汇编中,只能用第三种方法,即声明一个全局的指针,然后再加上两个全局函数.下面是汇编语言中需要增加的以及改变的代码,这个代码可以单独保存在一个文件中,例如Add_Code.asm文件,然后再在LList.asm中包含该文件进行编译并链接,也可以把下面的代码直接复制到LList.asm文件的相应段中进行编译链接.但是不管用什么方法进行编译链接,都要记住在原LList.asm文件中要修改的两个地方.
.data
pstFreeList PLList NULL
.code
;///////////////////////////////////////////////////////////
;用于创建内存,如果在栈中有内存,那么优先使用栈中的内存,如果没
;空闲的内存,才用GlobalAlloc这一API函数进行分配并返回.如果内存
;申请成功(不管是从哪里得到的内存空间),都在eax中返回内存首地址.
;如果eax==NULL,那么表示内存申请失败.
;///////////////////////////////////////////////////////////
CreateMemory proc uses esi dwSize:dword ;dwSize是要分配的内存字节数.
mov esi,pstFreeList
or esi,esi
je M_Empty ;栈中没有可用的内存.
mov eax,esi ;如果有空闲的内存,那么使eax指向该内存块.
mov esi,(LList ptr[esi]).next
mov pstFreeList,esi ;使栈顶指针指向下一个节点.
jmp Quit ;返回内存空间起始地址.
M_Empty:
invoke GlobalAlloc,GPTR,dwSize
or eax,eax
jne Quit ;如果eax!=NULL,那么分配内存成功.
xor eax,eax
Quit:
ret
CreateMemory endp
;///////////////////////////////////////////////////////////
;用于释放内存空间.它实际上不是真正的释放,只是把这一节的内存
;与另外的一个内存空间相连接,以便我们能够快速的进行分配以及释
;放内存.在这里要注意一点,那就是当栈为空时,当有一个节点删除时
;这个第一个栈的节点的next域的值一定要为NULL,否则分配以及释放
;内存的工作不会正常.
;///////////////////////////////////////////////////////////
FreeMemory proc uses esi ebx pstLList:ptr LList ;pstLList指针是要释放的内存首指针.
mov esi,pstFreeList
mov ebx,pstLList
or esi,esi
je M_Empty ;当前链表为空.
mov (LList ptr[ebx]).next,esi ;将要删除的节点与栈中的首节点链接.
mov pstFreeList,ebx ;重定位栈顶地址.
jmp Quit
M_Empty:
mov (LList ptr[ebx]).next,NULL ;使栈中的第一个节点的next域为NULL.
mov pstFreeList,ebx ;栈顶地址指向第一个节点.
Quit:
ret
FreeMemory endp
;///////////////////////////////////////////////////////////
;该过程用于真正实现将所有节点所占的内存释放.它将使用API函数.
;但要注意该函数以及RemoveAll过程的区别.首先RemoveAll过程的作
;用是将节点内存移到栈中,而该函数的功能是将所有栈中的节点所占
;内存空间释放给系统.该过程式必须.
;///////////////////////////////////////////////////////////
GlobalMemory proc uses esi
local pstLList:ptr LList
mov esi,pstFreeList
temp:
or esi,esi
je Quit ;如果栈为空,那么返回.
mov pstLList,esi ;否则,先用一个临时的指针保存当前节点.
mov esi,(LList ptr[esi]).next ;然后使esi指向下一个节点.
push esi
invoke GlobalFree,pstLList ;释放临时节点.
pop esi
jmp temp
Quit:
ret
GlobalMemory endp
从上面可以看出,一共添加了三个过程以及一个全局的变量,不仅如此,在定义LList.asm文件时Init以及RemoveAll中调用的两个API函数GlobalAlloc以及GlobalFree要分别被CreateMemory和FreeMemory函数代替,而且在main过程结束之前要先调用RemoveAll过程然后才可以调用GlobFreeMemory过程才可以真正的释放内存空间.
所以汇编实现的自己的方法对节点内存的分配以及释放对于C++是更复杂的.
本文来源 我爱IT技术网 http://www.52ij.com/jishu/1763.html 转载请保留链接。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
