zoukankan      html  css  js  c++  java
  • 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++,这样形成了一个顺序储存的数组.

    至此,如何将指针加入管理类的原理,我们就基本看完了.
    总结一下,他费了半天劲,其实就是要保管一堆内存地址罢了.
    如果想做自己的内存管理,就可以学习他的思想,保管指针地址.
    如何动态释放这些指针呢,等下一篇再叙.

     

  • 相关阅读:
    在 Java 应用程序中绑定 Bean 和数据
    JAVA的23种设计模式
    Windows 2012 Server评估版本安装问题处理
    从程序员到CTO的Java技术路线图
    MyBatis 学习
    CRT【p3868】[TJOI2009]猜数字
    线段树+扫描线【bzoj1645】[USACO07OPEN]城市的地平线City Horizon
    数学【CF743C】Vladik and fractions
    贪心【CF1029E】Tree with Small Distances
    线段树+二进制位拆分【CF242E】XOR on Segment
  • 原文地址:https://www.cnblogs.com/zhepama/p/3795715.html
Copyright © 2011-2022 走看看