vector記憶體分配和回收機制

NO IMAGE

隨著對C 的深入學習,逐漸發現了一些很重要的知識點。本文重點講解C Vector記憶體機制和效率問題。

iOS移動端

記得之前做iOS移動端時,關於垃圾回收機制是這樣說的:

ARC的基本概念:

      ARC(AutoRefrenceCount)自動引用計數,當你在編譯程式時提供自動管理記憶體的功能,它會自動加入記憶體的控制程式碼,控制物件的生命週期。這是在iOS4以後引入的技術。此時你在程式碼中使用release,retain時就會報錯,也就是這些方法被禁用。當然你也可以在專案中進行設定,選擇重新啟用MRC(手動記憶體管理)。在Build Setting裡面把“Objective-C Automatic Reference Counting”設定為NO,就可以不啟用ARC。

垃圾回收機制的基本概念:

     與Java、.net語言相同,Objective-C2.0以後,也提供了垃圾回收機制。但在iOS移動終端裝置中,並不支援垃圾回收機制(取決於終端裝置的效能)。因此,iPhone並不能對記憶體進行自動垃圾回收機制(取決於終端裝置的效能)。因此,iPhone並不能對記憶體進行自動垃圾回收處理(中間模式autorelease)。我們需要注意垃圾回收機制並不是ARC,ARC也是需要管理記憶體的,只不過是隱式的管理記憶體,編譯器會在適當的地方自動插入retain,release和autorelease.

回到正題

C Vector中記憶體分配和回收機制

1.記憶體增長

vector所有的記憶體相關問題都可以歸結於它的記憶體增長策略。vector有一個特點就是:記憶體空間只會增長不會減少。vector有兩個函式,一個是capacity(),返回物件緩衝區(vector維護的記憶體空間)實際申請的空間大小,另一個size(),返回當前物件緩衝區儲存資料的個數。對於vector來說,capacity是永遠大於等於size的,但capacity和size相等時,vector就會擴容,capacity變大。

比如說vector最常用的push_back操作,它的整個過程是怎麼一個機制呢?這個問題經常在面試中出現。

這個問題其實很簡單,在呼叫push_back時,若當前容量已經不能夠放入新的元素(capacity=size),那麼vector會重新申請一塊記憶體,把之前的記憶體裡的元素拷貝到新的記憶體當中,然後把push_back的元素拷貝到新的記憶體中,最後要析構原有的vector並釋放原有的記憶體。所以說這個過程的效率是極低的,為了避免頻繁的分配記憶體,C 每次申請記憶體都會成倍的增長,例如之前是4,那麼重新申請後就是8,以此類推。當然不一定是成倍增長,比如在我的編譯器環境下實測是0.5倍增長,之前是4,重新申請後就是6。

2. 記憶體釋放

就像前面所說的,vector的記憶體空間是隻增加不減少的,我們常用的操作clear()和erase(),實際上只是減少了size(),清除了資料,並不會減少capacity,所以記憶體空間沒有減少。那麼如何釋放記憶體空間呢,正確的做法是swap()操作。

標準模板如下

  1. template < class T >  
  2. void ClearVector( vector< T >& vt )   
  3. {  
  4.     vector< T > vtTemp;   
  5.     veTemp.swap( vt );  
  6. }  

也可以簡單使用以下操作

  1. vector<Point>().swap(pointVec); //或者pointVec.swap(vector<Point> ())  

swap交換技巧實現記憶體釋放思想:vector()使用vector的預設建構函式建立臨時vector物件,再在該臨時物件上呼叫swap成員,swap呼叫之後原來vector佔用的空間就等於一個預設構造的物件的大小,臨時物件就具有原來物件v的大小,而該臨時物件隨即就會被析構,從而其佔用的空間也被釋放。

  1. std::vector<T>().swap(X)  
  2. 作用相當於:  
  3. {  
  4. std::vector<T>  temp(X);  
  5. temp.swap(X);  
  6. }  

交換之後,temp會被析構。

3.總結

由上可見,vector雖然是動態陣列,但是本質上和陣列沒什麼區別,頻繁的銷燬新建,效率很低,所以正確的做法是新建vector的時候初始化一個合適的大小(),回到了陣列的老路上。不過之後可以動態變化還是很方便,而且還有很多好用的函式。

vecotr是動態陣列,顧名思義他可以動態的增加自己的長度。

記憶體機制:

但是怎樣的增加自己的長度?

     vector有兩個函式一個是capacity()返回記憶體空間即緩衝區的大小,另一個是size()返回當前陣列中數的數量。vector增加元素來說,當容量已經不能放進資料了,那麼他會重新申請一塊記憶體,把之前的記憶體利用複製建構函式複製到新的記憶體當中,然後把新新增的內容放入後面,另外此時的他申請的記憶體空間是原來空間的2倍,我測得是2倍

 參考文章:http://www.cnblogs.com/zhaopAC/p/5404828.html

     緩衝區的釋放

     vecotor佔用的記憶體只增不減,erase只是將一段區間( earse(arr.begin(), arr.end() )的值清除掉或者將某個值( erase(x) )清除掉,但是對於記憶體空間來說是沒變化的

所有記憶體空間是在vector析構時候才能被系統回收。empty()用來檢測容器是否為空的,clear()可以清空所有元素。但是即使clear(),vector所佔用的記憶體空間依然如故,無法保證記憶體的回收。

複製程式碼
方法一:
vecotr<Type>().swap(arr) // 回收arr記憶體
方法二:
模板:
Template < class T >
void ClearVector( vector< T >& vt ) 
{
vector< T > vtTemp; 
veTemp.swap( vt );
}
呼叫模板函式
void ClearVector<vector< T> & vt)
{  
vector<int> temp;//臨時物件未初始化,其緩衝區大小為0,沒有資料  
arr.swap(temp);//與我們的物件交換資料,arr的緩衝區就沒了。  
}//臨時變數會被析構,temp呼叫vector解構函式釋放空間  
複製程式碼

—————————————————————————-

效能分析:

主要比較三種插入方式:

1、直接push_back() 2、 reserve(n) 預定n個空間,當然後續push_back()會增加,其中的值不確定, 3 resize(n, x)  申請n個空間初始化為x,

reserve只是保持一個最小的空間大小,而resize則是對緩衝區進行重新分配,裡面涉及到的判斷和記憶體處理比較多所以比reserve慢一些,對於資料數目可以確定的時候,先預設空間大小是很有必要的。直接push_back資料頻繁移動很是耗時