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内存管理研究一相关推荐

  1. Cocos2d-x内存管理研究二

    http://hi.baidu.com/tzkt623/item/46a26805adf7e938a3332a04   上一篇我们讲了内核是如何将指针加入管理类进行管理.这次我将分析一下内核是如何自动 ...

  2. Linux内存管理机制研究

    Linux内存管理机制研究 查看linux系统中处于free状态的内存有两个角度,一个是从内核的角度来看,一个是从应用层的角度来看的. 1.从内核的角度来看free的内存,就是内核目前可以直接分配到, ...

  3. 深度学习中的内存管理问题研究综述

    点击上方蓝字关注我们 深度学习中的内存管理问题研究综述 马玮良1,2, 彭轩1,2, 熊倩1,2, 石宣化1,2, 金海1,2 1 华中科技大学计算机科学与技术学院,湖北 武汉 430074 2 华中 ...

  4. Cocos2d之Ref类与内存管理使用详解

    一.简介 用C++和JAVA编写过程序的朋友一定会为两种语言不同的内存管理机制懊恼.JAVA程序运行在JVM之上,由JVM自动实现内存管理,开发者只管申请内存而不用手动释放内存.当JAVA中对象没有被 ...

  5. 深入研究glibc内存管理器原理及优缺点

    最近查清了线上内存占用过大和swap使用频繁的原因:由于linux使用的glibc使用内存池技术导致的堆外内存暴增,基于这个过程中学习和了解了glibc的内存管理原理,和大家分享,如有错误请及时指出. ...

  6. Delphi7 内存管理及 FastMM 研究 (对于EXE和DLL内存共享很有用) .

    [转] Delphi7 内存管理及 FastMM 研究 (对于EXE和DLL内存共享很有用) 故国之晚秋 发表于2010-12-06 19:34 浏览(32) 评论(0) 分类: 我的日记       ...

  7. [转] Delphi7 内存管理及 FastMM 研究

    Delphi7 内存管理及 FastMM 研究[转] 作者:刘国辉 一.引言       FastMM 是适用于delphi的第三方内存管理器,在国外已经是大名鼎鼎,在国内也有许多人在使用或者希望使用 ...

  8. oracle11g自动内存管理好吗,Oracle11G新特性的研究之【自动内存管理】

    让实例运行为自动内存管理模式下 SQL> show parameter sga NAME                                 TYPE        VALUE -- ...

  9. linux内核 -内存管理模块概图

    1.从进程(task)的角度来看内存管理 每个进程对应一个task_struct; 每个task_struct 里面包含指向mm_struct 的指针mm, mm_struct 里面的主要成员: a. ...

最新文章

  1. 在macOS Sierria 10.12.2上升级默认的vim
  2. 1048 Find Coins(二分法解法)
  3. WdatePicker,js日期插件 ,时间相加
  4. php源码安装空白,源代码安装完成后,页面一片空白?
  5. RESTORE DATABASE命令还原SQLServer 2005 数据库
  6. css用hover制作下拉菜单
  7. Firefox(火狐)下载时卡在最后1秒解决办法
  8. (转)如何在windows 2008 安装IIS
  9. Git 切换远程仓库地址三种方法
  10. 如何正确删除TFS上项目
  11. 计算机网络对英语教学的消极影响,浅谈计算机对英语写作和教学的影响
  12. Premiere Pro 中的键盘快捷键
  13. AD19导出bom表的方法(按照元件不同数值分类,重点信息突出)
  14. 新书推荐 |《企业私有云建设指南》
  15. 新建SpringCloud电商后台项目
  16. Oracle数据库PL/SQL块-存储函数和过程
  17. easyUI中表格实现导出excel功能
  18. java农夫过河_C语言实现农夫过河代码及解析
  19. IDEA中如何实现git的cherry-pick可视化操作?
  20. ABAP-逻辑数据库

热门文章

  1. Nuxt.js asyncData 多请求
  2. 使用Lombok简化你的代码
  3. select case when if 的一些用法
  4. 线程知识-ThreadLocal使用详解
  5. zz让你成功的九个心理定律
  6. 转 公有密匙 私有密匙
  7. 返回、取消与关闭的使用逻辑
  8. 早该改了,只是我们太穷了
  9. 利用jvisualvm分析JVM,进行性能调优
  10. mysql binlog2sql闪回数据