轉自http://blog.csdn.net/zhuweisky/article/details/416415
C++ 快速內存分配
――zhuweisky 2003.12.10
我們知道,C++中當我們用new在堆中創建一個對象時,會發生兩件事情。首先1調用operator new分配一定大小的內存空間,2然後在此空間上調用構造函數以形成對象。而operator new涉及到尋找合適內存的算法,往往,這個算法是比較費時間的,所以,如果我們的程序中需要不斷的產生和釋放某類型的對象,那麼operator new所累計起來的時間可能就會成爲阻礙程序性能提高的一個重要因素了。看來我們確實有必要爲這種情況提供一種恰當的方案以實現快速內存分配。
先看看我們所處的環境。我們有一個自定義的類型(即一個class),而我們的程序需要不斷的產生和釋放此類型的對象,而產生此對象時需要的operator new是比較費時間的。我們將在如何縮小operator new操作的時間這個問題上努力?再深入一步想,我們用new分配內存生成對象,然後釋放該內存,接着又重複同樣的故事。呵,你可能有方案了,是嗎?
用 布放 placement new 比較好;
是的,我們可以在釋放內存的時候,並不是真的釋放內存,而是將指向該內存的指針維護起來,等到需要分配內存,就不用再調用內存分配算法了,直接將維護的指針返回就行了。我們可以將這中策略封裝在一個基類MemoryManage中,如果你的自定義類比如Derived類需要採用這種內存分配方案,那麼讓Derived公共繼承MemoryManage就行了。
我們可以用一個內存塊鏈表來維護被釋放的內存塊。根據“IS_A”關係,即一個Derived對象就是一個MemoryManage對象,所以我們可以在MemoryManage類中添加一個(MemoryManage*)型的指針,讓其指向下一塊需要維護的內存。另外我們需要一個MemoryManage對象來維護這個內存塊鏈表的head,這可以使其成爲MemoryManage類的一個靜態成員。內存塊鏈表的佈局大致如下圖。
接下來,我們要重寫operator new和operator delete ,以覆蓋掉默認的內存分配方式。MemoryManage類的主要代碼如下。
class MemoryManage
{
public:
static MemoryManage* listhead ;
MemoryManage* next ;
MemoryManage(){ next = NULL ; }//設定next指針初值
void* operator new(size_t size) //重定義operator new
{
if(listhead == NULL)
{
return malloc(size) ;
}
else
{
void* p = (void*)listhead ;
listhead = listhead->next ;
cout<<"從鏈表中提出一個指針返回!"<<endl ;
return p ;
}
}
void operator delete(void* pp) //重定義operator delete
{
MemoryManage* pp1 = (MemoryManage*)pp ;
pp1->next = listhead ;
listhead = pp1 ;
cout<<"已經將需要釋放的空間添加到鏈表中!"<<endl ;
}
};
MemoryManage* MemoryManage::listhead = NULL ; //初始化靜態成員
如果我們想當鏈表中維護的內存個數超過一定數量N(比如50)時,如果還有對象要釋放,我們就不將其內存添加到鏈表中了,而是直接釋放掉。這可以在MemoryManage類中增加一個靜態成員count來實現。
首先,在MemoryManage類中聲明這個成員,然後初始化爲0。
static int MemoryManage ::count = 0 ;
然後改寫operator new和operator delete。
void* operator new(size_t size) //重定義operator new
{
if(count == 0) //相當於listhead == NULL ;
{
return malloc(size) ;
}
else
{
void* p = (void*)listhead ;
listhead = listhead->next ;
count-- ; // 減小鏈表中維護的內存塊計數
cout<<"從鏈表中提出一個指針返回!"<<endl ;
return p ;
}
}
void operator delete(void* pp) //重定義operator delete
{
if(count == 50)
{
free(pp) ;
}
else
{
MemoryManage* pp1 = (MemoryManage*)pp ;
pp1->next = listhead ;
listhead = pp1 ;
count++ ; //增加鏈表中維護的內存塊計數
cout<<"已經將需要釋放的空間添加到鏈表中!"<<endl ;
}
}
我們似乎應該提供一個函數,讓用戶在適當的時候,可以釋放掉內存塊鏈表中維護的所有內存。於是在MemoryManage類中增加一個靜態的freeAllSpace函數。如下
static void freeAllSpace(void)
{
MemoryManage* temp = NULL;
int i= 0 ;
while(listhead != NULL)
{
temp = listhead->next ;
free(listhead) ;
cout<<"第"<<(++i)<<"次釋放空間"<<endl ;
listhead = temp ;
}
}
似乎一切都很好的解決了,是嗎?好,來看看下面的例子。
class Student:public MemoryManage
{
private:
string name ;
public:
Student(string ss):name(ss){}
void display(){ cout<<"name: "<<this->name<<endl ; }
};
class Teacher:public MemoryManage
{
private:
string name ;
int age ;
public:
Teacher(string ss ,int aa):name(ss),age(aa){}
void display()
{
cout<<"this is a teacher , name:"<<this->name<<endl ;
cout<<"age: "<<this->age<<endl ;
}
};
void main(void)
{
Student* s1 =new Student("qiqi") ;
s1->display() ;
Student* s2 = new Student("wei") ;
s2->display() ;
delete s1 ;
delete s2 ;
Teacher* s3 = new Teacher("sky",42) ;
Teacher* s4 = new Teacher("feifei",36) ;
s3->display() ;
s4->display() ;
delete s3 ;
delete s4 ;
Student::freeAllSpace() ; //產生運行期內存錯誤!
Teacher::freeAllSpace() ; //產生運行期內存錯誤!
}
運行一下,得到了什麼結果?輸出好像是沒有什麼問題,而實際上內存管理已變得相當混亂。注意到sizeof(Teacher)比sizeof(Student)大,而在程序執行的過程中,我們將只有sizeof(Student)大小的空間分配給了一個大小爲sizeof(Teacher)的Teacher對象,這肯定會覆蓋掉原空間相鄰內存中的其它數據,如果被覆蓋掉的正好是重要的信息,那麼程序的運行結果是可想而知的。
難道沒有辦法了嗎?如果我們Student對象由一個鏈表來管理,而Teacher對象由另一個鏈表來管理,而且所有一切需要按此策略分配的類型的對象都由它自己的特定鏈表來管理,那就可以了啊。怎麼做了?其實我們只要將MemoryManage類聲明爲模板就可以了,像這樣:
template<class T>
class MemoryManage{... ...} //其它一切保持不變!
另外靜態成員的初始化要修改一下:
template<class T>
MemoryManage<T>* MemoryManage<T>::listhead = NULL ;
template<class T>
static int MemoryManage ::count = 0 ;
然後定義Student類和Teacher類時,讓它們以自己類型名作爲模板參數從MemoryManage繼承就可以了,像這樣:
class Student:public MemoryManage<Student>{... ...} //其它一切不變!
class Teacher:public MemoryManage< Teacher >{... ...} //其它一切不變!
再執行一下上面的main函數,你會發現已經可以完全正確運行了。因爲現在每一個類(如Student或Teacher)都有它自己的內存管理鏈表,這個鏈表中所有的指針類型是完全一樣的,當然,它們所指向的內存塊的大小也是一樣的。如此以來,前面出現內存管理混亂的問題就解決了。上面的解決方案很簡單,僅僅是將MemoryManage類聲明爲模板類就可以了。的確是很簡單,但是這後面隱藏的思想卻極具價值。在這裏再繼續深入討論這個問題就有離題的意味了,對於這個話題筆者也許可以在下一篇文章中另行討論。
只要我們在進行程序設計,特別是在進行系統級的程序設計時,內存管理是需要我們給予足夠關注的一個方面,其中一個基本的原則就是“時空互換”原則,也就是我們在設計時應權衡是用空間換取時間(即爲了提高運行的速度,而對內存的需求加大),還是反過來,用時間換取空間。本文所介紹的這種內存管理方法就是典型的用空間換時間的處理方法。靈活地使用“時空互換”原則,我們可以自己設計出多種內存管理方案和其對應的算法。