http://hi.baidu.com/tzkt623/item/46a26805adf7e938a3332a04

  上一篇我们讲了内核是如何将指针加入管理类进行管理.这次我将分析一下内核是如何自动释放指针的.
  不过在这之前,我们要先引入另一个类.Cocos2d-x的核心类之一CCDirector.这个类可以说是引擎最主要的类了,没有他,引擎内的所有东西都无法运转起来.由于这个类有点大,做的事情很多,我就不一一粘出来看了,我们只关心跟内存管理有关的函数.
  因为一部片子只需要一个导演,所以CCDirector也就是一个单例类,是多重继承自CCObject和TypeInfo,有什么用,暂时不管.不过是采用的二段构建的单例.什么是二段构建,说简单点,就是不在构造函数中做初始化工作,而在另一个函数里做初始化工作.这样做有什么好处?可以说没有好处,也可以说有好处.的看用的人是出于一种什么样的目的了.比如说我们这里,如果不用二段构建是不可能实现的.因为CCDirector是个抽象类,我们知道,抽象类是不能被实例化的,也就是说,你new XXX是不可能的,编译器直接弹错.所以要想子类拥有父类的初始化功能,那只能另外写一个了.
那我们是如何构建的CCDirector这个单例的呢?
static CCDisplayLinkDirector *s_SharedDirector = NULL;
CCDirector* CCDirector::sharedDirector(void)
{
   if (!s_SharedDirector)
   {
       s_SharedDirector = new CCDisplayLinkDirector();
       s_SharedDirector->init();
   }

return s_SharedDirector;
}
看见了吧,他其实是new了一个自己的子类,来完成自己的功能的.而这个子类里,就有我们非常重要的一个函数.
在CCDirector的初始化函数里,我们看到了一个熟悉的面孔
bool CCDirector::init(void)
{
   CCLOG("cocos2d: %s", cocos2dVersion());
   .........................................
   // create autorelease pool
   CCPoolManager::sharedPoolManager()->push();

return true;
}
他在整个内核开始运行之前,就初始化了一个内存管理类.之后,在他的析构函数里
CCDirector::~CCDirector(void)
{
   CCLOG("cocos2d: deallocing CCDirector %p", this);
    .....................
   // pop the autorelease pool
   CCPoolManager::sharedPoolManager()->pop();
   CCPoolManager::purgePoolManager();
    ...........................
}
用管理类执行了一个弹栈操作pop().不过看到这里,我有点不解,pop是弹出当前管理池并clear掉,那如果当前有几个管理池同时存在呢?只弹一次,后面几个怎么办?我们还是慢慢来吧.
purgePoolManager()其实是这样
void CCPoolManager::purgePoolManager()
{
   CC_SAFE_DELETE(s_pPoolManager);
}
他删掉当前单例的指针.这样整个单例所保存的数据都会被删除掉,所以也就不用pop所有的元素了.
然后这个CCDirector 中剩下的唯一跟内存有关的,也是最最重要的函数mainLoop(),从他的名字我们就能看出来他的重要性了--主循环.他是一个纯虚函数virtual void mainLoop(void) = 0,在上面提到的子类CCDisplayLinkDirector中被覆写了.
现在我们来看看这个类
class CCDisplayLinkDirector : public CCDirector
{
public:
   CCDisplayLinkDirector(void) 
       : m_bInvalid(false)
   {}

virtual void mainLoop(void);
   virtual void setAnimationInterval(double dValue);
   virtual void startAnimation(void);
   virtual void stopAnimation();

protected:
   bool m_bInvalid;
};
他其实就是覆写了抽象类的几个纯虚函数而已.并且通过注释,我们知道他还有些其他的功能和限制.
1.他负责显示并以一定的频率刷新计时器.
2.计时器和界面绘制都是通过一定的频率是同步进行的.
3.只支持每秒60,30,15帧设置.
这些不属于我们讨论范围,简答提一下,我们只关心重要的mainLoop()
void CCDisplayLinkDirector::mainLoop(void)
{
   if (m_bPurgeDirecotorInNextLoop)
   {
       m_bPurgeDirecotorInNextLoop = false;
       purgeDirector();
   }
   else if (! m_bInvalid)
    {
        drawScene();
    
        // release the objects
        CCPoolManager::sharedPoolManager()->pop();       
    }
}
这里在没有失效的状况下(即m_bInvalid不为真),他会执行管理池中的pop函数.至于何时m_bInvalid为真,其实是在这里
void CCDisplayLinkDirector::stopAnimation(void)
{
   m_bInvalid = true;
}
而上面的条件语句中的这个变量m_bPurgeDirecotorInNextLoop,我们从名字里就能看出来,他是否是结束CCDirector的一个标志.既然是mainLoop,那就一定要Loop起来,而这里并没有看到任何Loop的迹象.于是我在内核中查找一下mainLoop在哪里被用过.
int CCApplication::run()
{
   PVRFrameEnableControlWindow(false);

// Main message loop:
   MSG msg;
   LARGE_INTEGER nFreq;
   LARGE_INTEGER nLast;
   LARGE_INTEGER nNow;

QueryPerformanceFrequency(&nFreq);
   QueryPerformanceCounter(&nLast);

// Initialize instance and cocos2d.
   if (!applicationDidFinishLaunching())
   {
       return 0;
   }
   CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
   pMainWnd->centerWindow();
   ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
   while (1)
   {
       if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
       {
           // Get current time tick.
           QueryPerformanceCounter(&nNow);
           // If it's the time to draw next frame, draw it, else sleep awhile.
           if (nNow.QuadPart - nLast.QuadPart >m_nAnimationInterval.QuadPart)
           {
               nLast.QuadPart = nNow.QuadPart;
               CCDirector::sharedDirector()->mainLoop();
           }
           else
           {
               Sleep(0);
           }
           continue;
       }
       if (WM_QUIT == msg.message)
       {
           // Quit message loop.
           break;
       }
       // Deal with windows message.
       if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd,m_hAccelTable, &msg))
       {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
   }
   return (int) msg.wParam;
}
如此大的一个while(1),就是在这里循环的.这个run又是在哪里运行的呢?大家看工程里的main.cpp
int APIENTRY _tWinMain(HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPTSTR   lpCmdLine,
                      int      nCmdShow)
{
   UNREFERENCED_PARAMETER(hPrevInstance);
   UNREFERENCED_PARAMETER(lpCmdLine);

// create the application instance
   AppDelegate app;
   CCEGLView* eglView = CCEGLView::sharedOpenGLView();
   eglView->setFrameSize(960, 640);
   returnCCApplication::sharedApplication()->run();
}
在这里,WIN32平台下的入口函数中,我们的引擎就已经启动了.其他的功能,是启动的一些初始化工作,以及跨平台的东西,这里不在讨论范围之内,我们只管内存管理的东西.
好,基本的过程我都找到了,现在来理一下自动释放的思路.
  假设我们程序已经运行,并且已经存进指针了.那么mainLoop这个函数,在不受到阻碍的情况下,会一直执行,并且一直执行CCPoolManager::sharedPoolManager()->pop().这里我们再把这个pop搬出来看看.还有一个附带的clear().
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);
   }
}
void CCAutoreleasePool::clear()
{
   if(m_pManagedObjectArray->count() > 0)
   {
       //CCAutoreleasePool* pReleasePool;
       CCObject* pObj = NULL;
       CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
       {
           if(!pObj)
               break;
       --(pObj->m_uAutoReleaseCount);
        }
       m_pManagedObjectArray->removeAllObjects();
   }
}
1.如果没有有释放池,就不做任何事,我们有池,一开始就push了一个进去.
2.记录当前池中的指针个数,假设我们有3个.在栈中的顺序以及引用计数分别为4(最后一针),2(第二针),1(第一针);
3.清除掉当前池中的指针.释放的过程是,遍历池中每一个指针,将他们的自动释放计数减一,然后removeAllObject.看看removeAllObjects()干了些什么事吧.
void CCArray::removeAllObjects()
{
   ccArrayRemoveAllObjects(data);
}
额,他其实只是调用了一下ccArray的函数而已,而里面的参数data,就是ccArray结构体的指针,也就是我们当前的自动释放池.
/** Removes all objects from arr */
void ccArrayRemoveAllObjects(ccArray *arr)
{
   while( arr->num > 0 )
   {
       (arr->arr[--arr->num])->release();
   }
}
这个函数写着,从数组里移除所有的指针.其实不算是移除,不算是真正的移除,他只是遍历了一次储存指针的内存地址(即储存内存地址的内存块),并分别执行了一次release操作,即将我们指针的引用计数减1,如果减一之后为0了,那么就删除(delete)这个指针.

我来模拟一下他的运行过程:
1.找到第一个引用计数为4的指针(最后一针),将他的释放计数减1,变为0;然后找到第二针,释放计数减1,变为0;最后找到第一针,释放计数减1,变为0. 
2.执行removeAll操作,将栈中每一个指针的引用计数减1.找到最后一针,4->3,第二针2->1,第一针1->0,然后第一针执行delete this操作.即立刻执行析构函数.
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了,就等于什么都不做,只是例行C++内核过程.那前面一篇提到的m_uAutoReleaseCount什么时候会大一1,这个经过我的研究发现,他不能大于1,他只有0和1两个值,你把它定成bool类型的也可以,只是不方便运算.

呵呵,重点来了.别以为delete干了件跟牛X的事,其实很多初学者都被他的名字骗了.delete其实只干了一件事,那就是把指针指向的内存中的数据给删了,但是指针并没有消失.他任然存在.这就是传说中的野指针,瞎指!,如果使用它,可能就会出现莫名其妙的BUG.所以,一般delete后,都要将指针的赋值为NULL,让系统知道,这指针暂时没用了.不过这里是delete this,this这个指针是极平常又不平常的一个东西,每个类生成的时候,都会自动带一个this指针.this指向的其实就是这个类自己,那delete this就等于"自杀".C++中,允许一个类通过成员函数申请"自杀操作",不过你必须保证,这个类是new出来的,不是对象,而且"自杀"是并且必须是这个类执行的最后一个操作,之后不在对这个类做任何有关数据,以及检查指针等操作.(想对C++有更深的了解,大家可以去看侯捷先生的<<深度探索C++对象模型>>)那你说,我把delete this放在析构里就行了.NO,这是绝对不行的,delete this之后的第一个操作就是执行析构函数,如果再放进去,会形成代码无限循环,导致堆栈溢出.

这样看来,我们保存指针数组中,任然有3个指针存在,虽然有个已经废了........
3.mainLoop再运行一次,然后pop在次执行.不过看到这里我出现了疑问,因为前一次的pop操作,仅仅只是释放了指针所指向的内存,但并没有把指针赋值为NULL,所以,如果再次操作指针,岂不是变成操作野指针了!来吧,再看看代码.
通过把输出数据打印到控制台,我发现了一个现象.
CCPoolManager::pop 1
CCAutoreleasePool::clear 4
0
1
2
3
..................
CCPoolManager::pop 1
CCAutoreleasePool::clear 0
也就是说,在pop一次之后,当前释放池中所储存的指针全部被移除了,不是删除指针,只是从数组中把它们去除.

注意,以下为我个人分析过程中非常重要的一段!!!

这里我要重要提示一下,这里卡了我很久很久,也废了我很多的时间!大家还记得那个自动释放的计数么?就是这玩意m_uAutoReleaseCount,他在add中被加了1,在clear中被减一.开始我的想法是,这个值只要是1,就说明他被加入了自动管理池中管理,为0表示没有加入自动管理池.但是我发现我错了,我找遍了整个内核,都没有找到有哪个地方在判断这个值是否为1(除了CCObject的析构,不过那里没有实际意义).

也就是说,按照我推理的思路,在pop中清理一次指针后,你得把指针移除吧,所以我想到了removeXX函数,可是他们一次也没有被执行.但是,上面的信息中却显示了释放池中没有元素了.那是怎么释放的呢?我这时想到了那个释放计数,他们不是在clear中被归零了么,应该有个判断,找到归零的指针就删除,可惜我错了,我愣是没找到这样的或类似的方法.那这数组究竟是如何变成0的呢.

其实秘密在这里.还记得上面自动释放池执行的那个函数么?在clear中的removeAllObjects,他最终执行的函数是ccArrayRemoveAllObjects,而这个函数干的事我们都知道,那就是

   while( arr->num > 0 )
   {
       (arr->arr[--arr->num])->release();
   }

等一下,这里有个非常阴险的地方,不注意看完全看不出来,我就是没注意,就是这个东西!!!!--arr->num!!!!.最开始我仅仅认为这是循环遍历数组执行release操作.天哪,当我分析了一遍又一遍时,才发现,这就是让自动释放池数组变成0的原因所在!

内核作者并没有真正的把数组释放掉,而是强行把数组的元素个数归零了!!!!!

这句判断if(m_pManagedObjectArray->count()> 0),其中的count()也就是获得数组元素个数的函数,他的原型是这样的.

unsigned int CCArray::count()

{

   return data->num;

}

其中的data就是一个指向ccArray的指针,而ccArray结构体中的第一个参数就是num.

各位肯定还记得我说过的,只delete指针不赋值NULL,是没办法真正删除一个指针的,而他会成为野指针.作者仅仅执行了delete this,没有赋值NULL,clear中却还继续对野指针进行操作,但是整个引擎却没有出现丝毫的BUG.也就是这个原因,让我纠结了很久,以至于发现了如此坑爹的删除数组方式.

这里给大家介绍一下m_uAutoReleaseCount这个自动释放计数的前身.Cocos2d-x这个引擎其实是来源于Cocos2d,而这个引擎是苹果机专用,也就是用的Object-C,而带引用计数的指针是Object-C语言层面就支持的东西,在苹果里面,这个释放计数其实是布尔值. .而C++语言层面并不支持释放计数,也就是带引用计数的指针,所以只能模拟.这下好了,一模拟就模拟了个我目前为止觉得是不仅废材还费脑子的变量m_uAutoReleaseCount.我曾经试图控制他的值,不过引擎会报错.但是我实在是没找到有哪里在判断他的值了.除了那个无关紧要的~CCObject.求高手解答吧!

也就是说,按照内核的自动管理措施,确实可以释放掉不用的内存,但是,会生成一大堆的野指针.真是不负责任的自动管理.不过通过我简单的研究,这个自动管理类,确实没办法将野指针消除,只能让系统回收,算是他引擎的缺陷吧.要不然,Cocos2d的开发者也不会叫我们尽量不要用自动释放.

好啦,重要的分析结束啦!

下面呢,我就把整个自动管理的过程串起来,给大家一个清晰的思路了.

  1. 我们首先new一个类出来  CCSprite *pSprite = CCSprite::create(),这个静态工厂方法里,直接就执行了autorelease操作了.

  2. ,这里面的过程就是这样的:new操作将引用计数初始化为1,释放计数初始化为0, autorelease通过几次操作,将我们的pSprite,也就是他的内存地址存放进了自动释放池里.然后retain将引用计数加1.这时引用计数为2.这时,我们就可以理解,为什么后面有一个pObject->release()操作了.我们只是想把他加入自动管理,但并不想retain他.于是乎,引用计数还是1.

  3. 这时,我们执行addChild(pSprite),将我们的精灵加入节点中,这个操作也会将引用计数加1.来给大家看看代码.

  4. ……………………..

  5. ,我只挑重要的函数讲,这里执行了一个insertChild操作.

void CCNode::insertChild(CCNode* child, int z)

{

   m_bReorderChildDirty = true;

   ccArrayAppendObjectWithResize(m_pChildren->data, child);

   child->_setZOrder(z);

}

  1. ,我们上一篇才提到的函数ccArrayAppendObjectWithResize又出现了,他的出现,就意味着retain的出现.我就不继续粘代码了,那这里的m_pChildren就一定是一个CCArray的指针了.这时pSprite的引用计数为2.

  2. 假设这时我们的游戏中有且仅有一个精灵,也就是我们的pSprite,我们暂时把他看成是玩家,游戏中,玩家是我们主要操作的对象,加入自动释放后,就有被自动释放的可能,但是这是我们所不允许的.引擎自然也知道则一点.所以,这时候mainLoop执行一次,pop执行一次,我们当前有自动释放池,所以clear执行一次.clear中,释放计数被减1,归零,然后由于removeAllObjects的执行,我们的玩家pSprite执行一次release,引用计数减由2变成1.然后数组被强行归零.这时mainLoop再次执行的话,释放池中的元素个数就为0了(没有再添加其他的东西).

  3. ,这不是坑爹吗?释放池就是用来释放指针的,但是pSprite的引用计数还有1,是不会执行delete this操作的.你说对了,这就是为什么,我们还能操作玩家的原因,如果这个指针被释放了,我们岂不是在操作野指针了?那如何控制玩家?

其实说到这里,大家应该明白了.这个自动释放池,做的事情,仅仅是释放掉引用计数为1的指针.只要引用计数超过1,那就不会释放.做的只是将引用计数减一的操作.那我们结束游戏,或者切换场景时,那些引用计数大于1的指针,改如何释放呢? 分析到这里的,我个人也认为,这个内存管理算是比较坑的了.

仔细想一下,内存泄露的原因是什么,说简单点,就是只new而不delete.什么情况下会发生这样的情况呢?比如说,我们的一个射击游戏,要发射很多子弹,于是我们在一个界面里new很多子弹出来,但是子弹碰撞后不delete掉,以至于子弹越来越多,内存越占越大.然后我们突然切换场景Scene,因为场景的对象消亡了,所以场景的析构函数被执行,自动释放了他占的内存,但是我们的子弹并没有执行析构函数,所以一直把空间占着,那段内存就废掉了,于是乎内存泄露.

但是我们切换界面的时候,玩家的引用计数为1,并且不再释放池内了,那该如何释放?这里,我们就要看下CCNode的析构函数了.

CCNode::~CCNode(void)

{

    CCLOGINFO("cocos2d: deallocing" );

……………………………

    if(m_pChildren&& m_pChildren->count() > 0)

    {

        CCObject*child;

       CCARRAY_FOREACH(m_pChildren, child)

        {

            CCNode*pChild = (CCNode*) child;

            if(pChild)

            {

               pChild->m_pParent = NULL;

            }

        }

    }

    // children

   CC_SAFE_RELEASE(m_pChildren);

}

他帮我们完成了这一步.我们的玩家不是在Layer上的么?Layer又在Scene上,当Scene被替换掉时,会自动执行他的析构函数,再执行父类的析构函数,也就是上面这段,这其中的m_pChildren中就保存着指向Layer的指针,他将Layer的父类赋值为空,然后release掉m_pChildren.而这个m_pChildre是指向CCArray的指针, CCArray是CCObject的子类,初始化时引用计数被加了1,然后autorelease加入自动释放池. m_pChildren被初始化为NULL,他是这样创建的

void CCNode::addChild(CCNode *child,int zOrder, int tag)

{   

   if( ! m_pChildren )

   {

       this->childrenAlloc();

}

}

 

void CCNode::childrenAlloc(void)

{

   m_pChildren = CCArray::createWithCapacity(4);

   m_pChildren->retain();

}

一来就被retain一次,经过一次pop引用计数为1,所以不会被释放.而最后的CC_SAFE_RELEASE(m_pChildren),将他的引用计数变为0.执行delete this操作,进而执行析构函数.

CCArray::~CCArray()

{

   ccArrayFree(data);

}

析构函数里执行了一次释放操作.

void ccArrayFree(ccArray*& arr)

{

   if( arr == NULL ) 

   {

       return;

   }

         ccArrayRemoveAllObjects(arr);

         

         free(arr->arr);

         free(arr);

 

   arr = NULL;

}

这里又执行了一个remove操作,这个蒙了我很久的函数ccArrayRemoveAllObjects(arr),他用m_pChildren数组里的成员执行一次release操作,也就是layer->release(),因为我们的layer并没有手动retain过.所以他的引用计数减1变为0,然后执行delete this.回收内存.接着,保存layer的这个数组被free掉,然后m_pChildren被free掉,接着赋值为NULL,彻底删除指针.

这样一来,layer就彻底没有了.我们以此类推,存在layer上的东西,也就是储存在layer里m_pChildren中的什么CCSprite ,CCMenu,CCLabelTTF等等,都会在界面切换时被彻底删除掉.所以,内存管理不仅仅只是autorelease做的事情,节点CCNode其实承担了相当大一部分内存管理工作.相比起来,释放池做的工作,仅仅是担心我们使用局部指针变量时,忘记release的一种防范策略.

不过这也提醒了我们,如果我们new了一个局部的指针,并且手动retain了一下,那就必须在必要的地方手动release他一次,并且两个操作的次数必须一样.为什么呢?回顾一下上面分析的就知道了,最后仅仅只是release了一下而已,也就是说在不手动retain的情况下,我们的内存管理,最多能回收掉引用计数为2的指针,如果你手动retain了,那最后的那个release不足以把引用计数减到0,那么就内存泄露了………..

不过如果你执行的是退出游戏,那就无所谓了,现在的操作系统,都能在程序退出时,将他所占用的内存全部回收掉,就算是你new了一堆东西出来还不delete.

Cocos2d-x的内存管理到这里就分析完了,虽然没有我想想的那样智能,但是也让我学到很多内存管理的思想.我们下一篇内核分析再见~

后记:
        写完这篇文章后,我在做开发时又想了一下,其实这个自动释放池不能算是坑,他的目的是把我们new出来的指针的引用计数释放到1,从而让Scene切 换,Layer切换,退出程序时,真正的内存管理,也就是CCNode和CCObject的内存管理能顺利吧所有的指针都释放掉.
        所以总结一下,此内存管理的思路是,用autorelease将指针的释放计数控制在一定范围内(最大值是2,autorelease之后必须最大是1),以至于当界面切换,各个类执行remove操作,程序退出等情况下能将占用的内存全部释放掉.

转载于:https://www.cnblogs.com/zhepama/p/3795716.html

Cocos2d-x内存管理研究二相关推荐

  1. C语言内存管理内幕(二)----半自动内存管理策略

    2019独角兽企业重金招聘Python工程师标准>>> C语言内存管理内幕(二)----半自动内存管理策略 转载于:https://my.oschina.net/hengcai001 ...

  2. 内存管理(二) ARC

    内存管理(二) ARC 上篇我们介绍了MRC,本篇我们介绍下ARC ARC概述 ARC是一种编译器功能,它通过LLVM编译器和Runtime协作来进行自动管理内存.LLVM编译器会在编译时在合适的地方 ...

  3. Linux内存管理(二)

    Linux内存管理之二:Linux在X86上的虚拟内存管理 本文档来自网络,并稍有改动. 前言 Linux支持很多硬件运行平台,常用的有:Intel X86,Alpha,Sparc等.对于不能够通用的 ...

  4. 同样学习Linux, 为何差别这么大? - 论打通Linux进程和内存管理任督二脉

    穆赫兰道和内陆帝国 我在多年的工程生涯中发现很多工程师碰到一个共性的问题:Linux工程师很多,甚至有很多有多年工作经验,但是对一些关键概念的理解非常模糊,比如不理解CPU.内存资源等的真正分布,具体 ...

  5. 内存管理(二)-- linux 预留内存几种方法

    日常开发过程可能要预留一段物理内存出来提供特殊场景使用(独占一段内存不被系统所使用). 本文讲解3种预留内存的方法,以及对预留内存的使用. 文章目录 一.memblock方式预留内存 1.1 memb ...

  6. Cocos2d-X内存管理研究一

    http://hi.baidu.com/tzkt623/item/651ca7d7a0aff6e055347f67        半夜没事干,研究内核,作为我cocos2d-x的第一篇教程.cocos ...

  7. FreeRTOS--堆内存管理(二)

    堆内存管理代码具体实现 heap_1 内存申请函数 内存释放函数 heap_2 内存块 内存堆初始化函数 内存块插入函数 内存申请函数 判断是不是第一次申请内存 开始分配内存 内存释放函数 heap_ ...

  8. C++进阶——内存管理(二)

    C++ memory primitives 分配 释放 类型 可否重载 malloc free C函数 不可 new delete C++表达式 不可 ::operator new ::operato ...

  9. iPhone内存管理详细解说(二)

    继上一篇之后,再来讲述iPhone内存管理的细节. 四:动态内存管理 第四节就内容实质来说跟第三节的object的拥有和丢弃政策是一样的,不过是从以代码为引导的动态形式讲述.所以你会在这一节看到许多代 ...

最新文章

  1. led伏安特性实验误差分析_检测实验室误差分析知识汇编
  2. 数据产品经理:如何做需求管控?
  3. WPF/Silverlight Layout 系统概述——Measure(转)
  4. 关于ARM Cortex系列产品
  5. oracle11g exp导出问题:部分表导不出来
  6. html日期英文状态显示不出来,html 时间控件插件laydate, 显示时分,不显示秒
  7. echarts热力背景图_Echarts 图表中设置背景图片
  8. k8s问题 CrashLoopBackOff
  9. 密码学专题 证书和CA指令 申请证书|建立CA|CA操作|使用证书|验证证书
  10. HDU 6098 Inversion 思维
  11. 校园edu无网络访问解决方案
  12. Perl语言必看书籍推荐
  13. 120个常用货源网站,赶紧收藏!
  14. PE启动盘安装操作系统
  15. html中的css样式表达式,CSS表达式
  16. 如何成为优秀的网络工程师,怎么做到含金量高?
  17. 系统分析设计——如何识别类
  18. 自动取款机 冲正交易
  19. 电阻 电容 电感 磁珠的选型总结对比
  20. 【B2B2C多用户】WSTMart商城系统 V2.0.6更新版发布

热门文章

  1. 从零点五开始,做半个不能玩的小游戏(二)
  2. 荣耀V40值得购买吗?玩游戏是一把好手!
  3. day4 Python的selenium库
  4. ROS-Solidworks转URDF
  5. urllib2 python3错误?用from urllib import request来代替!
  6. python列表操作
  7. CSS实例:图片导航块
  8. 更改hadoop native库文件后datanode故障
  9. 戏说模式-追MM与设计模式 (转载)
  10. arp 不同网段 相同vlan_三层交换机,相同的网段,不同的VLAN ,怎么通信?