Cocos2d-X内存管理研究一
http://hi.baidu.com/tzkt623/item/651ca7d7a0aff6e055347f67
???????半夜沒事干,研究內核,作為我cocos2d-x的第一篇教程.cocos2dx是一個樹形結構的引擎,具體結構我暫時不分析,這里只講內存管理.網上的分析都是說個純理論,我深入代碼內核,給大家詳細講解.
最開始我們尋找一下這棵樹的最大的根節點CCZone.
class?CC_DLL?CCZone
{
public:
????CCZone(CCObject?*pObject?=?NULL);
public:
????CCObject?*m_pCopyObject;
};
他其實沒干什么事,就是一個簡單的賦值.
CCZone::CCZone(CCObject?*pObject)
{
????m_pCopyObject?=?pObject;
}
將當前的CCObjec付給自己的委托變量CCObject?*m_pCopyObject.
然后我們來看CCObject.
class?CC_DLL?CCObject?:?public?CCCopying
{
public:
????//?object?id,?CCScriptSupport?need?public?m_uID
????unsigned?int????????m_uID;
????//?Lua?reference?id
????int?????????????????m_nLuaID;
protected:
????//?count?of?references
????unsigned?int????????m_uReference;
????//?count?of?autorelease
????unsigned?int????????m_uAutoReleaseCount;
public:
????CCObject(void);
????virtual?~CCObject(void);
????
????void?release(void);
????void?retain(void);
????CCObject*?autorelease(void);
????CCObject*?copy(void);
????bool?isSingleReference(void);
????unsigned?int?retainCount(void);
????virtual?bool?isEqual(const?CCObject*?pObject);
????virtual?void?update(float?dt)?{CC_UNUSED_PARAM(dt);};
????
????friend?class?CCAutoreleasePool;
};
他干的事有點多,不過先不管,我們看他的父類CCCopying.
class?CC_DLL?CCCopying
{
public:
????virtual?CCObject*?copyWithZone(CCZone*?pZone);
};
這個類也沒干什么事,只是定義了一個返回類型為CCObject指針的虛函數.
來看實現
CCObject*?CCCopying::copyWithZone(CCZone?*pZone)
{
????CC_UNUSED_PARAM(pZone);
????CCAssert(0,?"not?implement");
????return?0;
}
直接返回了0,看似沒什么作用,斷言也規定了必須是0.........pZone參數未使用,這讓我想起了這個函數的調用方法,可能是傳他的函數地址到一個參數中.
?好了,現在看來,內存管理跟前面的父類關系不是很大,那我們直接看CCObject的成員函數.
public:
????CCObject(void);
????virtual?~CCObject(void);
????void?release(void);
????void?retain(void);
????CCObject*?autorelease(void);
????CCObject*?copy(void);
????bool?isSingleReference(void);
????unsigned?int?retainCount(void);
????friend?class?CCAutoreleasePool;
這幾個成員函數以及一個名叫CCAutoreleasePool的友元類是比較重要的東西了,我們一個一個看.
先看構造函數.
CCObject::CCObject(void)
:m_uAutoReleaseCount(0)
,m_uReference(1)?//?when?the?object?is?created,?the?reference?count?of?it?is?1
,m_nLuaID(0)
{
????static?unsigned?int?uObjectCount?=?0;
????m_uID?=?++uObjectCount;
}
將他m_uAutoReleaseCount的計數初始化為0,并將m_uReference引用計數初始化為1.m_nLuaID這個不在C++范圍之內,暫時不管.在函數內,他給一個靜態無符號整形計數uObjectCount賦值為0,并將m_uID賦值,不過這個我們不關心.
析構函數東西有點多,我只講重點
CCObject::~CCObject(void)
{
????//?if?the?object?is?managed,?we?should?remove?it
????//?from?pool?manager
????if?(m_uAutoReleaseCount?>?0)
????{
????????CCPoolManager::sharedPoolManager()->removeObject(this);
????}
????//?if?the?object?is?referenced?by?Lua?engine,?remove?it
.............................................
}
這里說,如果這個類被托管了,也就是m_uAutoReleaseCount大于0,就把這個類從管理池中刪除.那我們可以猜想,只要m_uAutoReleaseCount參數大于0,那么就說明此類被加入了內存管理系統,以至于m_uAutoReleaseCount是如何大于0的,大于1又會是什么樣的情況,后面在看.
接下來是release()
void?CCObject::release(void)
{
????CCAssert(m_uReference?>?0,?"reference?count?should?greater?than?0");
????--m_uReference;
????if?(m_uReference?==?0)
????{
????????delete?this;
????}
}
他就是把引用計數減一,如果引用計數為0了,那么就刪掉他.這里我們可以猜想,引用計數有可能大于1,至于為什么會大于1,慢慢看.
現在是retain()
void?CCObject::retain(void)
{
????CCAssert(m_uReference?>?0,?"reference?count?should?greater?than?0");
????++m_uReference;
}
他就是把引用計數加1,正好也解釋了引用計數為什么會大于1的情況.初始化類成功之后,引用計數為1,如果再retain一下,就大于1了.
接下來是比較重要的函數autorelease()
?CCObject*?CCObject::autorelease(void)
{
????CCPoolManager::sharedPoolManager()->addObject(this);
????return?this;
}
他把當前類加入管理池,返回一個被加入管理池中的指向CCObject的指針.也就是返回當前指針.
好了,后面的暫時不看了,我們找到了比較重要的東西了,這個CCPoolManager在內存管理里面扮演了重要的角色,我們現在去研究它.
class?CC_DLL?CCPoolManager
{
????CCArray*????m_pReleasePoolStack;????
????CCAutoreleasePool*????????????????????m_pCurReleasePool;
????CCAutoreleasePool*?getCurReleasePool();
public:
????CCPoolManager();
????~CCPoolManager();
????void?finalize();
????void?push();
????void?pop();
????void?removeObject(CCObject*?pObject);
????void?addObject(CCObject*?pObject);
????static?CCPoolManager*?sharedPoolManager();
????static?void?purgePoolManager();
????friend?class?CCAutoreleasePool;
};
首先,他不繼承自任何類,說明他是老大級的人物了,我們應該好好研究一番了.
不過一上來就看到三個委托.
CCArray*????m_pReleasePoolStack;????
CCAutoreleasePool*???m_pCurReleasePool;
CCAutoreleasePool*?getCurReleasePool();
這下有得看了.我們先知道他們存在就行了.還是先研究成員函數.
由于整個系統中,內存管理有并且只需有一個就夠了,所以這個類是個單例.什么是單例我就不說了.自己了解.
CCPoolManager::CCPoolManager()
{
????m_pReleasePoolStack?=?new?CCArray();????
????m_pReleasePoolStack->init();
????m_pCurReleasePool?=?0;
}
構造函數里,做了3件事,m_pReleasePoolStack參數new了一個CCArray出來,并且初始化了一下,意會一下他的名字,釋放池棧.然后給?m_pCurReleasePool這個指針初始化為0,說明當前還沒有自動內存管理的池.不過這里我有點不明白,就是init().在CCArray()的構造函數里已經調用過一次,為何還來一次,難道有BUG?
接下來是析構
CCPoolManager::~CCPoolManager()
{
????
?????finalize();
?
?????//?we?only?release?the?last?autorelease?pool?here?
????m_pCurReleasePool?=?0;
?????m_pReleasePoolStack->removeObjectAtIndex(0);
?
?????CC_SAFE_DELETE(m_pReleasePoolStack);
}
析構里調用了一個finalize(),并且說,只release最后一個自動管理池(第一個進棧的).那我們先不管,來看看這個finalize()
void?CCPoolManager::finalize()
{
????if(m_pReleasePoolStack->count()?>?0)
????{
????????//CCAutoreleasePool*?pReleasePool;
????????CCObject*?pObj?=?NULL;
????????CCARRAY_FOREACH(m_pReleasePoolStack,?pObj)
????????{
????????????if(!pObj)
????????????????break;
????????????CCAutoreleasePool*?pPool?=?(CCAutoreleasePool*)pObj;
????????????pPool->clear();
????????}
????}
}
他做的事就是如果自動釋放的池中有東西,那就全部clear掉.這個clear是個什么,暫時不管,我們先把其他的看完.
管理池最大的作用就是管理添加到他里面的指針,所以我們先看看添加函數
void?CCPoolManager::addObject(CCObject*?pObject)
{
????getCurReleasePool()->addObject(pObject);
}
他說,我其實只是把你們傳進來的指針加在當前的釋放池里了.那這個getCurReleasePool()又是個什么玩意.
CCAutoreleasePool*?CCPoolManager::getCurReleasePool()
{
????if(!m_pCurReleasePool)
????{
????????push();
????}
????CCAssert(m_pCurReleasePool,?"current?auto?release?pool?should?not?be?null");
????return?m_pCurReleasePool;
}
他是一個返回類型為指向CCAutoreleasePool的指針的函數,他干了什么呢?如果當前沒有創建釋放池,那么push()一個進去.并且斷言釋放池必須有.最后返回這個自動釋放池的指針.
那么我們猜也能猜到push()干了什么了,無非就是new了一個CCAutoreleasePool出來.
void?CCPoolManager::push()
{
????CCAutoreleasePool*?pPool?=?new?CCAutoreleasePool();???????//ref?=?1
????m_pCurReleasePool?=?pPool;
????m_pReleasePoolStack->addObject(pPool);???????????????????//ref?=?2
????pPool->release();???????????????????????????????????????//ref?=?1
}
new出來之后,將自動釋放池委托給當前管理類,并把它加入了釋放池棧中.然后release掉自己.
有push那肯定就有pop
void?CCPoolManager::pop()
{
????if?(!m_pCurReleasePool)
????{
????????return;
????}
?????int?nCount?=?m_pReleasePoolStack->count();
????m_pCurReleasePool->clear();
??????if(nCount?>?1)
??????{
????????m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
????????m_pCurReleasePool?=?(CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount?-?2);
????}
????/*m_pCurReleasePool?=?NULL;*/
}
他 的功能是,如果當前沒有釋放池,那就什么事也不干return掉.如果有值,記錄下當前總共有多少個釋放池.并且clear掉當前釋放池.如果當前釋放池 的數量大于1,那么,移除最后一個釋放池.為什么是最后一個,因為管理池是個棧,先進后出,最后進去的是排在出口第一個位置,并且計算機都是以0開始計數 的,所以在減1才是最后一個位置的元素.然后把棧中倒數第二個元素(彈棧后的當前池)賦給當前管理池的參數m_pCurReleasePool.
那么,現在該removeObject了
void?CCPoolManager::removeObject(CCObject*?pObject)
{
????CCAssert(m_pCurReleasePool,?"current?auto?release?pool?should?not?be?null");
????m_pCurReleasePool->removeObject(pObject);
}
他只是把當前傳入池中的指針變量移除.
CCPoolManager看完了,這里最重要的就是CCAutoreleasePool,那么我們轉去看他.
與管理類比起來,釋放池就簡單多了.
class?CC_DLL?CCAutoreleasePool?:?public?CCObject
{
????CCArray*????m_pManagedObjectArray;????
public:
????CCAutoreleasePool(void);
????~CCAutoreleasePool(void);
????void?addObject(CCObject?*pObject);
????void?removeObject(CCObject?*pObject);
????void?clear();
};
不過他卻繼承自CCObject.這是為什么,至少目前我們可以看出來有一點,在CCPoolManager::push()中他用到了release(),而這個函數是CCObject中定義的,要用它繼承是個好辦法.不過在CCObject中已經申明了CCAutoreleasePool為他的友元類了,就可以完全訪問CCObject中所有的數據.這里又繼承一下,是什意思?還記得上面的一段代碼么m_pReleasePoolStack->addObject(pPool)對,他要自己管理自己,所以得繼承自CCObject,但是CCObject無法把將自己的私有成員繼承給他,所以只能友元解決.
CCAutoreleasePool::CCAutoreleasePool(void)
{
????m_pManagedObjectArray?=?new?CCArray();
????m_pManagedObjectArray->init();
}
此類構造函數很簡單,也是new了一個CCArray()出來,然后init()一下.
CCAutoreleasePool::~CCAutoreleasePool(void)
{
????CC_SAFE_DELETE(m_pManagedObjectArray);
}
析構刪除它.
然后就是我們見過很多次但從未見過真身的addObject
void?CCAutoreleasePool::addObject(CCObject*?pObject)
{
????m_pManagedObjectArray->addObject(pObject);
????CCAssert(pObject->m_uReference?>?1,?"reference?count?should?be?greater?than?1");
????++(pObject->m_uAutoReleaseCount);
????pObject->release();?//?no?ref?count,?in?this?case?autorelease?pool?added.
}
他將當前指針加入一個CCArray數組中.并且斷言引用計數必須大于1.并且將自動釋放計數加1,讓其受到自動釋放池的管理.還記得上面說到m_uAutoReleaseCount怎樣才會大于0么,這里就揭示是原因所在.最后竟然release了一次指針.后面寫著,這里的引用計數應該是0,但是加入自動釋放池時加了1.這是怎么加的1?然后還有引用計數如何大于1的?我們先不著急,看完其他函數再來研究.
下一個自然就是remove了
void?CCAutoreleasePool::removeObject(CCObject*?pObject)
{
????for?(unsigned?int?i?=?0;?i?<?pObject->m_uAutoReleaseCount;?++i)
????{
????????m_pManagedObjectArray->removeObject(pObject,?false);
????}
}
這個函數是遍歷所有釋放計數,然后remove掉所有元素,這里的removeObject(pObject,?false)是CCArray中的函數,我們暫時不管.
最后一個函數clear
void?CCAutoreleasePool::clear()
{
????if(m_pManagedObjectArray->count()?>?0)
????{
????????//CCAutoreleasePool*?pReleasePool;
#ifdef?_DEBUG
????????int?nIndex?=?m_pManagedObjectArray->count()?-?1;
#endif
????????CCObject*?pObj?=?NULL;
????????CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray,?pObj)
????????{
????????????if(!pObj)
????????????????break;
????????????--(pObj->m_uAutoReleaseCount);
#ifdef?_DEBUG
????????????nIndex--;
#endif
????????}
????????m_pManagedObjectArray->removeAllObjects();
}
這里是,如果指針管理數組里有東西,那就遍歷所有的指針,將釋放計數減到0,最后刪掉所有數組中的東西.
自 此,cocos2d-x的內存管理類就全部瀏覽完畢了,除了一個CCArray,不過通過名字,和他的作用,我們就能清楚的知道,他一定是一個繼承自 CCObject的數組,否者是不能存放CCObject類型的指針的.不過這個不重要,這套內存管理是如何運行的,還有上面的疑問到底是怎么回事才是最 重要的.
????????接下來我們就來理一下這個內存管理的思路吧.
???????? 1.由于引擎是樹狀的,那么我們每new一個類出來,也就是沒生成一個指針,就會調用它所有父類的構造函數一次.于是乎,CCObject這個最大的節 點,每次都會執行一次構造函數,將3個參數初始化.并且給m_uID賦值,由于uObjectCount是靜態無符號整形,那么就說明每一個新new出來 的節點,都有自己唯一的ID,所以我們寫程序的時候,最好不要去修改m_uID這個參數,雖然他是public,因為當東西多了之后,難免會出現BUG.
????????2.我們將new出來的指針,執行autorelease()操作,也就是把當前new出來的指針加入了內存池管理類CCPoolManager和自動釋放類CCAutoreleasePool中.放入其中時,其實只執行了一個函數,CCAutoreleasePool中的addObject,他的作用就是把釋放計數加1,但是這里斷言引用計數必須大與1,并且通過控制臺,我發現他確實大于1.但是new出CCObject時,引用計數只是1,那這增加引用計數的地方在哪呢?
???????????通過注釋我們可以發現,每addObject一次,引用計數就會被加1.那么,就一定是這個add干的事.addObject是CCArray的方法,我們轉到CCArray中查看,發現他其實是這樣的.
void?CCArray::addObject(CCObject*?object)
{
????ccArrayAppendObjectWithResize(data,?object);
}
他也只干了一件是,就是生成一個指定的大小的ccArray,注意這里是小寫的,這個ccArray是C語言寫的,他只是一個結構體.
typedef?struct?_ccArray?{
????unsigned?int?num,?max;
????CCObject**?arr;
}?ccArray;
那這個CCObject**?arr變量是什么意思呢.我 們知道X?*p是指針,那X? **p,就是指向指針的指針,統稱多級指針.怎么理解呢,我們都知道,指針指向的是內存地址,當我們需要運用哪一塊內存中的內容時,指針就指向那一塊內存 地址,以此提取出內存中的數據來用.那么指向指針的指針其實就可以這樣理解:還存在一個指針,他指向我們當前使用的指針,這個指針指向的內存中所保存的數 據,是我們當前使用的指針指向的內存地址.
這里為什么要這樣聲明,從上面的自動釋放類中,我們可以得到啟示.自動釋放類保管的是函數指針,而這么多的指針,是通過一個可擴大的動態數組來保管,那么這個數組的本質,就是保管的一堆內存地址.如何保管內存地址呢?多級指針就可以幫你完成.
?/**?Appends?an?object.?Capacity?of?arr?is?increased?if?needed.?*/
void?ccArrayAppendObjectWithResize(ccArray?*arr,?CCObject*?object)
{
????ccArrayEnsureExtraCapacity(arr,?1);
????ccArrayAppendObject(arr,?object);
}
這個函數就是ccArray中的函數了,附加一個對象,如果需要,數組大小可以動態擴展.細心的朋友可能發現了,這個函數沒有作用域!!也就是沒有前面的XXXX::這一段.這就表明他是一個全局函數,C語言中,沒有類的概念,自然都是全局函數.那么我們一個一個看.
void?ccArrayEnsureExtraCapacity(ccArray?*arr,?unsigned?int?extra)
{
????while?(arr->max?<?arr->num?+?extra)
????{
????????ccArrayDoubleCapacity(arr);
????}
}
從他的名字我們就能看出來他的功能,確定分配額外的大小.如果數組最大的大小小于數組元素個數加額外空間的大小,那就分配雙倍的數組空間.
void?ccArrayDoubleCapacity(ccArray?*arr)
{
????arr->max?*=?2;
????CCObject**?newArr?=?(CCObject**)realloc(?arr->arr,?arr->max?*?sizeof(CCObject*)?);
????//?will?fail?when?there's?not?enough?memory
????CCAssert(newArr?!=?0,?"ccArrayDoubleCapacity?failed.?Not?enough?memory");
????arr->arr?=?newArr;
}
這么一來,先將數組最大空間變成雙倍.然后新建一個CCObject**?newArr.執行realloc,他是一個C語言函數.
給大家看一下他的原型:void?*realloc(void?*mem_address,?unsigned?int?newsize);
用法:指針名=(數據類型*)realloc(要改變內存大小的指針名,新的大小)
這樣,我們就把一個保管指向CCObject類指針內存地址的內存塊,擴大了兩倍.然后返回這個內存塊的地址.
接下來的才是重頭戲.
/**?Appends?an?object.?Behavior?undefined?if?array?doesn't?have?enough?capacity.?*/
void?ccArrayAppendObject(ccArray?*arr,?CCObject*?object)
{
????CCAssert(object?!=?NULL,?"Invalid?parameter!");
????object->retain();
????arr->arr[arr->num]?=?object;
????arr->num++;
}
他說,附加一個對象,如果數組大小不夠了的話,會發生未知的行為...............真坑爹......不過這里,我們見到了我們一直想見的東西.retain()終于出現了,現在,我們就可以解釋,為什么m_uReference會大于1了.arr->arr[arr->num]?=?object是什么意思呢?還是多級指針問題,arr 是指向儲存指針(實為內存地址)的內存.這里要牽涉到數組了,其實一位數組等價于,指向一段連續儲存的內存首地址的指針,即我們使用a[3]時編譯器自動 會將其變成指針運算*(a?+?3),其實3后面還隱藏了東西,是*? sizeof(type),這里是內存尋址原理,首地址加上偏移量,等于當前想找的內存地址,偏移量就是數據類型的大小,比如int為4個字節,那么每塊 內存數據塊的大小就是4個字節,如果總共有16字節,那么就是儲存了4個數據塊,每4字節做為偏移.
所以這里也是一樣的,編譯器自動把他變成*(arr?+?arr->num),意思是,找到這個內存塊指向的地址,這里面準備裝的是我們new出來的指針的內存地址,所以,就把object,也就是我們add進去的指針的內存地址放了進去,然后num++,這樣形成了一個順序儲存的數組.
總結一下,他費了半天勁,其實就是要保管一堆內存地址罷了.
如果想做自己的內存管理,就可以學習他的思想,保管指針地址.
如何動態釋放這些指針呢,等下一篇再敘.
?
總結
以上是生活随笔為你收集整理的Cocos2d-X内存管理研究一的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python startswith
- 下一篇: scjp考试准备 - 2 - 逻辑运算及