欧美天堂在线观看,国产亚洲女人久久久久毛片,亚洲色图综合久久http://m.shnenglu.com/michaelgao/category/7305.htmlzh-cnMon, 02 Feb 2009 23:56:50 GMTMon, 02 Feb 2009 23:56:50 GMT60C++線程池的概念http://m.shnenglu.com/michaelgao/archive/2009/02/02/72810.htmlmicheal's techmicheal's techMon, 02 Feb 2009 08:28:00 GMThttp://m.shnenglu.com/michaelgao/archive/2009/02/02/72810.htmlhttp://m.shnenglu.com/michaelgao/comments/72810.htmlhttp://m.shnenglu.com/michaelgao/archive/2009/02/02/72810.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/72810.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/72810.html
光榮在于平淡,艱巨因?yàn)槁L

【轉(zhuǎn)】一個簡單的線程池(c++版)

#ifndef _ThreadPool_H_
#define _ThreadPool_H_
#pragma warning(disable: 4530)
#pragma warning(disable: 4786)
#include <cassert>
#include <vector>
#include <queue>
#include <windows.h>
class ThreadJob  //工作基類
{
public:
    //供線程池調(diào)用的虛函數(shù)
    virtual void DoJob(void *pPara) = 0;
};
class ThreadPool
{
public:
    //dwNum 線程池規(guī)模
    ThreadPool(DWORD dwNum = 4) : _lThreadNum(0), _lRunningNum(0)
    {
        InitializeCriticalSection(&_csThreadVector);
        InitializeCriticalSection(&_csWorkQueue);
        _EventComplete = CreateEvent(0, false, false, NULL);
        _EventEnd = CreateEvent(0, true, false, NULL);
        _SemaphoreCall = CreateSemaphore(0, 0,  0x7FFFFFFF, NULL);
        _SemaphoreDel =  CreateSemaphore(0, 0,  0x7FFFFFFF, NULL);
        assert(_SemaphoreCall != INVALID_HANDLE_VALUE);
        assert(_EventComplete != INVALID_HANDLE_VALUE);
        assert(_EventEnd != INVALID_HANDLE_VALUE);
        assert(_SemaphoreDel != INVALID_HANDLE_VALUE);
        AdjustSize(dwNum <= 0 ? 4 : dwNum);
    }
    ~ThreadPool()
    {
        DeleteCriticalSection(&_csWorkQueue);
        CloseHandle(_EventEnd);
        CloseHandle(_EventComplete);
        CloseHandle(_SemaphoreCall);
        CloseHandle(_SemaphoreDel);
        vector<ThreadItem*>::iterator iter;
        for(iter = _ThreadVector.begin(); iter != _ThreadVector.end(); iter++)
        {
            if(*iter)
                delete *iter;
        }
        DeleteCriticalSection(&_csThreadVector);
    }
    //調(diào)整線程池規(guī)模
    int AdjustSize(int iNum)
    {
        if(iNum > 0)
        {
            ThreadItem *pNew;
            EnterCriticalSection(&_csThreadVector);
            for(int _i=0; _i<iNum; _i++)
            {
                _ThreadVector.push_back(pNew = new ThreadItem(this));
                assert(pNew);
                pNew->_Handle = CreateThread(NULL, 0, DefaultJobProc, pNew, 0, NULL);
                assert(pNew->_Handle);
            }
            LeaveCriticalSection(&_csThreadVector);
        }
        else
        {
            iNum *= -1;
            ReleaseSemaphore(_SemaphoreDel,  iNum > _lThreadNum ? _lThreadNum : iNum, NULL);
        }
        return (int)_lThreadNum;
    }
    //調(diào)用線程池
    void Call(void (*pFunc)(void  *), void *pPara = NULL)
    {
        assert(pFunc);
        EnterCriticalSection(&_csWorkQueue);
        _JobQueue.push(new JobItem(pFunc, pPara));
        LeaveCriticalSection(&_csWorkQueue);
        ReleaseSemaphore(_SemaphoreCall, 1, NULL);
    }
    //調(diào)用線程池
    inline void Call(ThreadJob * p, void *pPara = NULL)
    {
        Call(CallProc, new CallProcPara(p, pPara));
    }
    //結(jié)束線程池, 并同步等待
    bool EndAndWait(DWORD dwWaitTime = INFINITE)
    {
        SetEvent(_EventEnd);
        return WaitForSingleObject(_EventComplete, dwWaitTime) == WAIT_OBJECT_0;
    }
    //結(jié)束線程池
    inline void End()
    {
        SetEvent(_EventEnd);
    }
    inline DWORD Size()
    {
        return (DWORD)_lThreadNum;
    }
    inline DWORD GetRunningSize()
    {
        return (DWORD)_lRunningNum;
    }
    bool IsRunning()
    {
        return _lRunningNum > 0;
    }
protected:
    //工作線程
    static DWORD WINAPI DefaultJobProc(LPVOID lpParameter = NULL)
    {
        ThreadItem *pThread = static_cast<ThreadItem*>(lpParameter);
        assert(pThread);
        ThreadPool *pThreadPoolObj = pThread->_pThis;
        assert(pThreadPoolObj);
        InterlockedIncrement(&pThreadPoolObj->_lThreadNum);
        HANDLE hWaitHandle[3];
        hWaitHandle[0] = pThreadPoolObj->_SemaphoreCall;
        hWaitHandle[1] = pThreadPoolObj->_SemaphoreDel;
        hWaitHandle[2] = pThreadPoolObj->_EventEnd;
        JobItem *pJob;
        bool fHasJob;
        for(;;)
        {
            DWORD wr = WaitForMultipleObjects(3, hWaitHandle, false, INFINITE);
            //響應(yīng)刪除線程信號
            if(wr == WAIT_OBJECT_0 + 1) 
                break;
            //從隊(duì)列里取得用戶作業(yè)
            EnterCriticalSection(&pThreadPoolObj->_csWorkQueue);
            if(fHasJob = !pThreadPoolObj->_JobQueue.empty())
            {
                pJob = pThreadPoolObj->_JobQueue.front();
                pThreadPoolObj->_JobQueue.pop();
                assert(pJob);
            }
            LeaveCriticalSection(&pThreadPoolObj->_csWorkQueue);
            //受到結(jié)束線程信號確定是否結(jié)束線程(結(jié)束線程信號&& 是否還有工作)
            if(wr == WAIT_OBJECT_0 + 2 && !fHasJob) 
                break;
            if(fHasJob && pJob)
            {
                InterlockedIncrement(&pThreadPoolObj->_lRunningNum);
                pThread->_dwLastBeginTime = GetTickCount();
                pThread->_dwCount++;
                pThread->_fIsRunning = true;
                pJob->_pFunc(pJob->_pPara); //運(yùn)行用戶作業(yè)
                delete pJob;
                pThread->_fIsRunning = false;
                InterlockedDecrement(&pThreadPoolObj->_lRunningNum);
            }
        }
        //刪除自身結(jié)構(gòu)
        EnterCriticalSection(&pThreadPoolObj->_csThreadVector);
    pThreadPoolObj->_ThreadVector.erase(find(pThreadPoolObj->_ThreadVector.begin(), pThreadPoolObj->_ThreadVector.end(), pThread));
        LeaveCriticalSection(&pThreadPoolObj->_csThreadVector);
        delete pThread;
        InterlockedDecrement(&pThreadPoolObj->_lThreadNum);
        if(!pThreadPoolObj->_lThreadNum)  //所有線程結(jié)束
            SetEvent(pThreadPoolObj->_EventComplete);
        return 0;
    }
    //調(diào)用用戶對象虛函數(shù)
    static void CallProc(void *pPara)
    {
        CallProcPara *cp = static_cast<CallProcPara *>(pPara);
        assert(cp);
        if(cp)
        {
            cp->_pObj->DoJob(cp->_pPara);
            delete cp;
        }
    }
    //用戶對象結(jié)構(gòu)
    struct CallProcPara 
    {
        ThreadJob* _pObj;//用戶對象
        void *_pPara;//用戶參數(shù)
        CallProcPara(ThreadJob* p, void *pPara) : _pObj(p), _pPara(pPara) { };
    };
    //用戶函數(shù)結(jié)構(gòu)
    struct JobItem
    {
        void (*_pFunc)(void  *);//函數(shù)
        void *_pPara; //參數(shù)
        JobItem(void (*pFunc)(void  *) = NULL, void *pPara = NULL) : _pFunc(pFunc), _pPara(pPara) { };
    };
    //線程池中的線程結(jié)構(gòu)
    struct ThreadItem
    {
        HANDLE _Handle; //線程句柄
        ThreadPool *_pThis;  //線程池的指針
        DWORD _dwLastBeginTime; //最后一次運(yùn)行開始時間
        DWORD _dwCount; //運(yùn)行次數(shù)
        bool _fIsRunning;
        ThreadItem(ThreadPool *pthis) : _pThis(pthis), _Handle(NULL), _dwLastBeginTime(0), _dwCount(0), _fIsRunning(false) { };
        ~ThreadItem()
        {
            if(_Handle)
            {
                CloseHandle(_Handle);
                _Handle = NULL;
            }
        }
    };
    std::queue<JobItem *> _JobQueue;  //工作隊(duì)列
    std::vector<ThreadItem *>  _ThreadVector; //線程數(shù)據(jù)
    CRITICAL_SECTION _csThreadVector, _csWorkQueue; //工作隊(duì)列臨界, 線程數(shù)據(jù)臨界
    HANDLE _EventEnd, _EventComplete, _SemaphoreCall, _SemaphoreDel;//結(jié)束通知, 完成事件, 工作信號,刪除線程信號
    long _lThreadNum, _lRunningNum; //線程數(shù), 運(yùn)行的線程數(shù)
};
#endif //_ThreadPool_H_

使用說明1:

調(diào)用方法

void threadfunc(void *p)
{
     YourClass* yourObject = (YourClass*)    p;
 //
}
 ThreadPool tp;
 for(i=0; i<100; i++)
  tp.Call(threadfunc);
ThreadPool tp(20);//20為初始線程池規(guī)模
 tp.Call(threadfunc, lpPara);

使用時注意幾點(diǎn):

1. ThreadJob  沒什么用,直接寫線程函數(shù)吧。 

2. 線程函數(shù)(threadfunc)的入口參數(shù)void* 可以轉(zhuǎn)成自定義的類型對象,這個對象可以記錄下線程運(yùn)行中的數(shù)據(jù),并設(shè)置線程當(dāng)前狀態(tài),以此與線程進(jìn)行交互。

3. 線程池有一個EndAndWait函數(shù),用于讓線程池中所有計(jì)算正常結(jié)束。有時線程池中的一個線程可能要運(yùn)行很長時間,怎么辦?可以通過線程函數(shù)threadfunc的入口參數(shù)對象來處理,比如:

class YourClass 
{
  int cmd; // cmd = 1是上線程停止計(jì)算,正常退出。
};
threadfunc(void* p) {
  YourClass* yourObject = (YourClass*)p;
  while (true) {
    // do some calculation
    if (yourClass->cmd == 1)
      break;
  }
}

在主線程中設(shè)置yourClass->cmd = 1,該線程就會自然結(jié)束。

使用說明2:

Code
void threadfunc(void *p)
{
 //
}
 ThreadPool tp;
 for(i=0; i<100; i++)
  tp.Call(threadfunc);
 ThreadPool tp(20);//20為初始線程池規(guī)模
 tp.Call(threadfunc, lpPara);
 tp.AdjustSize(50);//增加50
 tp.AdjustSize(-30);//減少30

 

class MyThreadJob : public ThreadJob //線程對象從ThreadJob擴(kuò)展
{
public:
 virtual void DoJob(void *p)//自定義的虛函數(shù)
 {
  //.
 }
};
 MyThreadJob mt[10];
 ThreadPool tp;
 for(i=0; i<100 i++)
  tp.Call(mt + i);//tp.Call(mt + i, para);



]]>
多線程同步機(jī)制摘要http://m.shnenglu.com/michaelgao/archive/2009/02/02/72805.htmlmicheal's techmicheal's techMon, 02 Feb 2009 06:35:00 GMThttp://m.shnenglu.com/michaelgao/archive/2009/02/02/72805.htmlhttp://m.shnenglu.com/michaelgao/comments/72805.htmlhttp://m.shnenglu.com/michaelgao/archive/2009/02/02/72805.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/72805.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/72805.html

多線程同步機(jī)制摘要

Posted on 2007-04-03 19:34 kk 閱讀(1306) 評論(0)  編輯 收藏 引用 所屬分類: IT   Critical Section

Critical section(臨界區(qū))用來實(shí)現(xiàn)“排他性占有”。適用范圍是單一進(jìn)程的各線程之間。它是:

·         一個局部性對象,不是一個核心對象。

·         快速而有效率。

·         不能夠同時有一個以上的critical section被等待。

·         無法偵測是否已被某個線程放棄。

Mutex

Mutex是一個核心對象,可以在不同的線程之間實(shí)現(xiàn)“排他性占有”,甚至幾十那些現(xiàn)成分屬不同進(jìn)程。它是:

·         一個核心對象。

·         如果擁有mutex的那個線程結(jié)束,則會產(chǎn)生一個“abandoned”錯誤信息。

·         可以使用Wait…()等待一個mutex。

·         可以具名,因此可以被其他進(jìn)程開啟。

·         只能被擁有它的那個線程釋放(released)。

Semaphore

Semaphore被用來追蹤有限的資源。它是:

·         一個核心對象。

·         沒有擁有者。

·         可以具名,因此可以被其他進(jìn)程開啟。

·         可以被任何一個線程釋放(released)。

Event Object

Event object通常使用于overlapped I/O,或用來設(shè)計(jì)某些自定義的同步對象。它是:

·         一個核心對象。

·         完全在程序掌控之下。

·         適用于設(shè)計(jì)新的同步對象。

·         “要求蘇醒”的請求并不會被儲存起來,可能會遺失掉。

·         可以具名,因此可以被其他進(jìn)程開啟。

Interlocked Variable

如果Interlocked…()函數(shù)被使用于所謂的spin-lock,那么他們只是一種同步機(jī)制。所謂spin-lock是一種busy loop,被預(yù)期在極短時間內(nèi)執(zhí)行,所以有最小的額外負(fù)擔(dān)(overhead)。系統(tǒng)核心偶爾會使用他們。除此之外,interlocked variables主要用于引用技術(shù)。他們:

·         允許對4字節(jié)的數(shù)值有些基本的同步操作,不需動用到critical section或mutex之類。

·         在SMP(Symmetric Multi-Processors)操作系統(tǒng)中亦可有效運(yùn)作。




]]>
placement new釋疑http://m.shnenglu.com/michaelgao/archive/2009/01/08/71527.htmlmicheal's techmicheal's techThu, 08 Jan 2009 12:56:00 GMThttp://m.shnenglu.com/michaelgao/archive/2009/01/08/71527.htmlhttp://m.shnenglu.com/michaelgao/comments/71527.htmlhttp://m.shnenglu.com/michaelgao/archive/2009/01/08/71527.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/71527.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/71527.html

"placement new"? Embarrassed它 到底是什么東東呀?我也是最近幾天才聽說,看來對于C++我還差很遠(yuǎn)呀!placement new 是重載operator new的一個標(biāo)準(zhǔn)、全局的版本,它不能被自定義的版本代替(不像普通的operator new和operator delete能夠被替換成用戶自定義的版本)。

它的原型如下:
void *operator new( size_t, void *p ) throw()  { return p; }

首先我們區(qū)分下幾個容易混淆的關(guān)鍵詞:new、operator new、placement new
new和delete操作符我們應(yīng)該都用過,它們是對中的內(nèi)存進(jìn)行申請和釋放,而這兩個都是不能被重載的。要實(shí)現(xiàn)不同的內(nèi)存分配行為,需要重載operator new,而不是new和delete。I dont know

看如下代碼:
class MyClass {…};
MyClass * p=new MyClass;

這里的new實(shí)際上是執(zhí)行如下3個過程:


1. 調(diào)用operator new分配內(nèi)存 ;2. 調(diào)用構(gòu)造函數(shù)生成類對象;3. 返回相應(yīng)指針。

operator new就像operator+一樣,是可以重載的,但是不能在全局對原型為void operator new(size_t size)這個原型進(jìn)行重載,一般只能在類中進(jìn)行重載。如果類中沒有重載operator new,那么調(diào)用的就是全局的::operator new來完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重載的,一般你重載的其中一個,那么最后把其余的三個都重載一遍。

至于placement new才是本文的重點(diǎn)。其實(shí)它也只是operator new的一個重載的版本,只是我們很少用到它。如果你想在已經(jīng)分配的內(nèi)存中創(chuàng)建一個對象,使用new時行不通的。也就是說placement new允許你在一個已經(jīng)分配好的內(nèi)存中(棧或者堆中)構(gòu)造一個新的對象。原型中void*p實(shí)際上就是指向一個已經(jīng)分配好的內(nèi)存緩沖區(qū)的的首地址。

我們知道使用new操作符分配內(nèi)存需要在堆中查找足夠大的剩余空間,這個操作速度是很慢的,而且有可能出現(xiàn)無法分配內(nèi)存的異常(空間不夠)。 placement new就可以解決這個問題。我們構(gòu)造對象都是在一個預(yù)先準(zhǔn)備好了的內(nèi)存緩沖區(qū)中進(jìn)行,不需要查找內(nèi)存,內(nèi)存分配的時間是常數(shù);而且不會出現(xiàn)在程序運(yùn)行中途 出現(xiàn)內(nèi)存不足的異常。所以,placement new非常適合那些對時間要求比較高,長時間運(yùn)行不希望被打斷的應(yīng)用程序。

使用方法如下:
1. 緩沖區(qū)提前分配
可以使用堆的空間,也可以使用棧的空間,所以分配方式有如下兩種:
class MyClass {…};
char *buf=new char[N*sizeof(MyClass)+ sizeof(int) ] ; 或者char buf[N*sizeof(MyClass)+ sizeof(int) ];

2. 對象的構(gòu)造
MyClass * pClass=new(buf) MyClass;

3. 對象的銷毀
一旦這個對象使用完畢,你必須顯式的調(diào)用類的析構(gòu)函數(shù)進(jìn)行銷毀對象。但此時內(nèi)存空間不會被釋放,以便其他的對象的構(gòu)造。
pClass->~MyClass();

4. 內(nèi)存的釋放
如果緩沖區(qū)在堆中,那么調(diào)用delete[] buf;進(jìn)行內(nèi)存的釋放;如果在棧中,那么在其作用域內(nèi)有效,跳出作用域,內(nèi)存自動釋放。

注意:

  • 在C++標(biāo)準(zhǔn)中,對于placement operator new []有如下的說明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我們必須申請比原始對象大小多出sizeof(int)個字節(jié)來存放對象的個數(shù),或者說數(shù)組的大小。
  • 使用方法第二步中的new才是placement new,其實(shí)是沒有申請內(nèi)存的,只是調(diào)用了構(gòu)造函數(shù),返回一個指向已經(jīng)分配好的內(nèi)存的一個指針,所以對象銷毀的時候不需要調(diào)用delete釋放空間,但必須調(diào)用析構(gòu)函數(shù)銷毀對象。



]]>
內(nèi)存池http://m.shnenglu.com/michaelgao/archive/2008/11/17/67122.htmlmicheal's techmicheal's techMon, 17 Nov 2008 08:37:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/11/17/67122.htmlhttp://m.shnenglu.com/michaelgao/comments/67122.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/11/17/67122.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/67122.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/67122.html
本章首先簡單介紹自定義內(nèi)存池性能優(yōu)化的原理,然后列舉軟件開發(fā)中常用的內(nèi)存池的不同類型,并給出具體實(shí)現(xiàn)的實(shí)例。

引言

本 書主要針對的是 C++ 程序的性能優(yōu)化,深入介紹 C++ 程序性能優(yōu)化的方法和實(shí)例。全書由 4 個篇組成,第 1 篇介紹 C++ 語言的對象模型,該篇是優(yōu)化 C++ 程序的基礎(chǔ);第 2 篇主要針對如何優(yōu)化 C++ 程序的內(nèi)存使用;第 3 篇介紹如何優(yōu)化程序的啟動性能;第 4 篇介紹了三類性能優(yōu)化工具,即內(nèi)存分析工具、性能分析工具和 I/O 檢測工具,它們是測量程序性能的利器。

在此我們推出了此書的第 26 章供大家在線瀏覽。更多推薦書籍請?jiān)L問 developerWorks 圖書頻道





回頁首


6.1 自定義內(nèi)存池性能優(yōu)化的原理


書名:《C++應(yīng)用程序性能優(yōu)化》
作者:馮宏華、徐瑩、程遠(yuǎn)、汪磊 等編著
出版社:電子工業(yè)出版社
出版日期:2007 年 03 月
ISBN:978-7-121-03831-0
購買: 中國互動出版網(wǎng)dearbook

推薦章節(jié):


如 前所述,讀者已經(jīng)了解到"堆"和"棧"的區(qū)別。而在編程實(shí)踐中,不可避免地要大量用到堆上的內(nèi)存。例如在程序中維護(hù)一個鏈表的數(shù)據(jù)結(jié)構(gòu)時,每次新增或者刪 除一個鏈表的節(jié)點(diǎn),都需要從內(nèi)存堆上分配或者釋放一定的內(nèi)存;在維護(hù)一個動態(tài)數(shù)組時,如果動態(tài)數(shù)組的大小不能滿足程序需要時,也要在內(nèi)存堆上分配新的內(nèi)存 空間。

6.1.1 默認(rèn)內(nèi)存管理函數(shù)的不足

利用默認(rèn)的內(nèi)存管理函數(shù)new/delete或malloc/free在堆上分配和釋放內(nèi)存會有一些額外的開銷。

系 統(tǒng)在接收到分配一定大小內(nèi)存的請求時,首先查找內(nèi)部維護(hù)的內(nèi)存空閑塊表,并且需要根據(jù)一定的算法(例如分配最先找到的不小于申請大小的內(nèi)存塊給請求者,或 者分配最適于申請大小的內(nèi)存塊,或者分配最大空閑的內(nèi)存塊等)找到合適大小的空閑內(nèi)存塊。如果該空閑內(nèi)存塊過大,還需要切割成已分配的部分和較小的空閑 塊。然后系統(tǒng)更新內(nèi)存空閑塊表,完成一次內(nèi)存分配。類似地,在釋放內(nèi)存時,系統(tǒng)把釋放的內(nèi)存塊重新加入到空閑內(nèi)存塊表中。如果有可能的話,可以把相鄰的空 閑塊合并成較大的空閑塊。

默認(rèn)的內(nèi)存管理函數(shù)還考慮到多線程的應(yīng)用,需要在每次分配和釋放內(nèi)存時加鎖,同樣增加了開銷。

可見,如果應(yīng)用程序頻繁地在堆上分配和釋放內(nèi)存,則會導(dǎo)致性能的損失。并且會使系統(tǒng)中出現(xiàn)大量的內(nèi)存碎片,降低內(nèi)存的利用率。

默認(rèn)的分配和釋放內(nèi)存算法自然也考慮了性能,然而這些內(nèi)存管理算法的通用版本為了應(yīng)付更復(fù)雜、更廣泛的情況,需要做更多的額外工作。而對于某一個具體的應(yīng)用程序來說,適合自身特定的內(nèi)存分配釋放模式的自定義內(nèi)存池則可以獲得更好的性能。

6.1.2 內(nèi)存池的定義和分類

自 定義內(nèi)存池的思想通過這個"池"字表露無疑,應(yīng)用程序可以通過系統(tǒng)的內(nèi)存分配調(diào)用預(yù)先一次性申請適當(dāng)大小的內(nèi)存作為一個內(nèi)存池,之后應(yīng)用程序自己對內(nèi)存的 分配和釋放則可以通過這個內(nèi)存池來完成。只有當(dāng)內(nèi)存池大小需要動態(tài)擴(kuò)展時,才需要再調(diào)用系統(tǒng)的內(nèi)存分配函數(shù),其他時間對內(nèi)存的一切操作都在應(yīng)用程序的掌控 之中。

應(yīng)用程序自定義的內(nèi)存池根據(jù)不同的適用場景又有不同的類型。

從 線程安全的角度來分,內(nèi)存池可以分為單線程內(nèi)存池和多線程內(nèi)存池。單線程內(nèi)存池整個生命周期只被一個線程使用,因而不需要考慮互斥訪問的問題;多線程內(nèi)存 池有可能被多個線程共享,因此則需要在每次分配和釋放內(nèi)存時加鎖。相對而言,單線程內(nèi)存池性能更高,而多線程內(nèi)存池適用范圍更廣。

從內(nèi)存池可分配內(nèi)存單元大小來分,可以分為固定內(nèi)存池和可變內(nèi)存池。所謂固定內(nèi)存池是指應(yīng)用程序每次從內(nèi)存池中分配出來的內(nèi)存單元大小事先已經(jīng)確定,是固定不變的;而可變內(nèi)存池則每次分配的內(nèi)存單元大小可以按需變化,應(yīng)用范圍更廣,而性能比固定內(nèi)存池要低。

6.1.3 內(nèi)存池工作原理示例

下面以固定內(nèi)存池為例說明內(nèi)存池的工作原理,如圖6-1所示。


圖6-1 固定內(nèi)存池
圖6-1  固定內(nèi)存池

固定內(nèi)存池由一系列固定大小的內(nèi)存塊組成,每一個內(nèi)存塊又包含了固定數(shù)量和大小的內(nèi)存單元。

如 圖6-1所示,該內(nèi)存池一共包含4個內(nèi)存塊。在內(nèi)存池初次生成時,只向系統(tǒng)申請了一個內(nèi)存塊,返回的指針作為整個內(nèi)存池的頭指針。之后隨著應(yīng)用程序?qū)?nèi)存 的不斷需求,內(nèi)存池判斷需要動態(tài)擴(kuò)大時,才再次向系統(tǒng)申請新的內(nèi)存塊,并把所有這些內(nèi)存塊通過指針鏈接起來。對于操作系統(tǒng)來說,它已經(jīng)為該應(yīng)用程序分配了 4個等大小的內(nèi)存塊。由于是大小固定的,所以分配的速度比較快;而對于應(yīng)用程序來說,其內(nèi)存池開辟了一定大小,內(nèi)存池內(nèi)部卻還有剩余的空間。

例 如放大來看第4個內(nèi)存塊,其中包含一部分內(nèi)存池塊頭信息和3個大小相等的內(nèi)存池單元。單元1和單元3是空閑的,單元2已經(jīng)分配。當(dāng)應(yīng)用程序需要通過該內(nèi)存 池分配一個單元大小的內(nèi)存時,只需要簡單遍歷所有的內(nèi)存池塊頭信息,快速定位到還有空閑單元的那個內(nèi)存池塊。然后根據(jù)該塊的塊頭信息直接定位到第1個空閑 的單元地址,把這個地址返回,并且標(biāo)記下一個空閑單元即可;當(dāng)應(yīng)用程序釋放某一個內(nèi)存池單元時,直接在對應(yīng)的內(nèi)存池塊頭信息中標(biāo)記該內(nèi)存單元為空閑單元即 可。

可見與系統(tǒng)管理內(nèi)存相比,內(nèi)存池的操作非常迅速,它在性能優(yōu)化方面的優(yōu)點(diǎn)主要如下。

(1)針對特殊情況,例如需要頻繁分配釋放固定大小的內(nèi)存對象時,不需要復(fù)雜的分配算法和多線程保護(hù)。也不需要維護(hù)內(nèi)存空閑表的額外開銷,從而獲得較高的性能。

(2)由于開辟一定數(shù)量的連續(xù)內(nèi)存空間作為內(nèi)存池塊,因而一定程度上提高了程序局部性,提升了程序性能。

(3)比較容易控制頁邊界對齊和內(nèi)存字節(jié)對齊,沒有內(nèi)存碎片的問題。





回頁首


6.2 一個內(nèi)存池的實(shí)現(xiàn)實(shí)例

本節(jié)分析在某個大型應(yīng)用程序?qū)嶋H應(yīng)用到的一個內(nèi)存池實(shí)現(xiàn),并詳細(xì)講解其使用方法與工作原理。這是一個應(yīng)用于單線程環(huán)境且分配單元大小固定的內(nèi)存池,一般用來為執(zhí)行時會動態(tài)頻繁地創(chuàng)建且可能會被多次創(chuàng)建的類對象或者結(jié)構(gòu)體分配內(nèi)存。

本節(jié)首先講解該內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)聲明及圖示,接著描述其原理及行為特征。然后逐一講解實(shí)現(xiàn)細(xì)節(jié),最后介紹如何在實(shí)際程序中應(yīng)用此內(nèi)存池,并與使用普通內(nèi)存函數(shù)申請內(nèi)存的程序性能作比較。

6.2.1 內(nèi)部構(gòu)造

內(nèi)存池類MemoryPool的聲明如下:


class MemoryPool
{
private:
MemoryBlock* pBlock;
USHORT nUnitSize;
USHORT nInitSize;
USHORT nGrowSize;

public:
MemoryPool( USHORT nUnitSize,
USHORT nInitSize = 1024,
USHORT nGrowSize = 256 );
~MemoryPool();

void* Alloc();
void Free( void* p );
};

MemoryBlock為內(nèi)存池中附著在真正用來為內(nèi)存請求分配內(nèi)存的內(nèi)存塊頭部的結(jié)構(gòu)體,它描述了與之聯(lián)系的內(nèi)存塊的使用信息:


struct MemoryBlock
{
USHORT nSize;
USHORT nFree;
USHORT nFirst;
USHORT nDummyAlign1;
MemoryBlock* pNext;
char aData[1];

static void* operator new(size_t, USHORT nTypes, USHORT nUnitSize)
{
return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize);
}
static void operator delete(void *p, size_t)
{
::operator delete (p);
}

MemoryBlock (USHORT nTypes = 1, USHORT nUnitSize = 0);
~MemoryBlock() {}
};

此內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)如圖6-2所示。


圖6-2 內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)
圖6-2  內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)

6.2.2 總體機(jī)制

此內(nèi)存池的總體機(jī)制如下。

(1) 在運(yùn)行過程中,MemoryPool內(nèi)存池可能會有多個用來滿足內(nèi)存申請請求的內(nèi)存塊,這些內(nèi)存塊是從進(jìn)程堆中開辟的一個較大的連續(xù)內(nèi)存區(qū)域,它由一個 MemoryBlock結(jié)構(gòu)體和多個可供分配的內(nèi)存單元組成,所有內(nèi)存塊組成了一個內(nèi)存塊鏈表,MemoryPool的pBlock是這個鏈表的頭。對每 個內(nèi)存塊,都可以通過其頭部的MemoryBlock結(jié)構(gòu)體的pNext成員訪問緊跟在其后面的那個內(nèi)存塊。

(2) 每個內(nèi)存塊由兩部分組成,即一個MemoryBlock結(jié)構(gòu)體和多個內(nèi)存分配單元。這些內(nèi)存分配單元大小固定(由MemoryPool的 nUnitSize表示),MemoryBlock結(jié)構(gòu)體并不維護(hù)那些已經(jīng)分配的單元的信息;相反,它只維護(hù)沒有分配的自由分配單元的信息。它有兩個成員 比較重要:nFree和nFirst。nFree記錄這個內(nèi)存塊中還有多少個自由分配單元,而nFirst則記錄下一個可供分配的單元的編號。每一個自由 分配單元的頭兩個字節(jié)(即一個USHORT型值)記錄了緊跟它之后的下一個自由分配單元的編號,這樣,通過利用每個自由分配單元的頭兩個字節(jié),一個 MemoryBlock中的所有自由分配單元被鏈接起來。

(3)當(dāng)有新的內(nèi)存請求到來 時,MemoryPool會通過pBlock遍歷MemoryBlock鏈表,直到找到某個MemoryBlock所在的內(nèi)存塊,其中還有自由分配單元 (通過檢測MemoryBlock結(jié)構(gòu)體的nFree成員是否大于0)。如果找到這樣的內(nèi)存塊,取得其MemoryBlock的nFirst值(此為該內(nèi) 存塊中第1個可供分配的自由單元的編號)。然后根據(jù)這個編號定位到該自由分配單元的起始位置(因?yàn)樗蟹峙鋯卧笮」潭ǎ虼嗣總€分配單元的起始位置都可 以通過編號分配單元大小來偏移定位),這個位置就是用來滿足此次內(nèi)存申請請求的內(nèi)存的起始地址。但在返回這個地址前,需要首先將該位置開始的頭兩個字節(jié)的 值(這兩個字節(jié)值記錄其之后的下一個自由分配單元的編號)賦給本內(nèi)存塊的MemoryBlock的nFirst成員。這樣下一次的請求就會用這個編號對應(yīng) 的內(nèi)存單元來滿足,同時將此內(nèi)存塊的MemoryBlock的nFree遞減1,然后才將剛才定位到的內(nèi)存單元的起始位置作為此次內(nèi)存請求的返回地址返回 給調(diào)用者。

(4)如果從現(xiàn)有的內(nèi)存塊中找不到一個自由的內(nèi)存分配單元(當(dāng)?shù)?次請求內(nèi)存,以及現(xiàn)有的所有內(nèi)存 塊中的所有內(nèi)存分配單元都已經(jīng)被分配時會發(fā)生這種情形),MemoryPool就會從進(jìn)程堆中申請一個內(nèi)存塊(這個內(nèi)存塊包括一個MemoryBlock 結(jié)構(gòu)體,及緊鄰其后的多個內(nèi)存分配單元,假設(shè)內(nèi)存分配單元的個數(shù)為n,n可以取值MemoryPool中的nInitSize或者nGrowSize), 申請完后,并不會立刻將其中的一個分配單元分配出去,而是需要首先初始化這個內(nèi)存塊。初始化的操作包括設(shè)置MemoryBlock的nSize為所有內(nèi)存 分配單元的大小(注意,并不包括MemoryBlock結(jié)構(gòu)體的大小)、nFree為n-1(注意,這里是n-1而不是n,因?yàn)榇舜涡聝?nèi)存塊就是為了滿足 一次新的內(nèi)存請求而申請的,馬上就會分配一塊自由存儲單元出去,如果設(shè)為n-1,分配一個自由存儲單元后無須再將n遞減1),nFirst為1(已經(jīng)知道 nFirst為下一個可以分配的自由存儲單元的編號。為1的原因與nFree為n-1相同,即立即會將編號為0的自由分配單元分配出去。現(xiàn)在設(shè)為1,其后 不用修改nFirst的值),MemoryBlock的構(gòu)造需要做更重要的事情,即將編號為0的分配單元之后的所有自由分配單元鏈接起來。如前所述,每個 自由分配單元的頭兩個字節(jié)用來存儲下一個自由分配單元的編號。另外,因?yàn)槊總€分配單元大小固定,所以可以通過其編號和單元大小(MemoryPool的 nUnitSize成員)的乘積作為偏移值進(jìn)行定位。現(xiàn)在唯一的問題是定位從哪個地址開始?答案是MemoryBlock的aData[1]成員開始。因 為aData[1]實(shí)際上是屬于MemoryBlock結(jié)構(gòu)體的(MemoryBlock結(jié)構(gòu)體的最后一個字節(jié)),所以實(shí)質(zhì)上,MemoryBlock結(jié) 構(gòu)體的最后一個字節(jié)也用做被分配出去的分配單元的一部分。因?yàn)檎麄€內(nèi)存塊由MemoryBlock結(jié)構(gòu)體和整數(shù)個分配單元組成,這意味著內(nèi)存塊的最后一個 字節(jié)會被浪費(fèi),這個字節(jié)在圖6-2中用位于兩個內(nèi)存的最后部分的濃黑背景的小塊標(biāo)識。確定了分配單元的起始位置后,將自由分配單元鏈接起來的工作就很容易 了。即從aData位置開始,每隔nUnitSize大小取其頭兩個字節(jié),記錄其之后的自由分配單元的編號。因?yàn)閯傞_始所有分配單元都是自由的,所以這個 編號就是自身編號加1,即位置上緊跟其后的單元的編號。初始化后,將此內(nèi)存塊的第1個分配單元的起始地址返回,已經(jīng)知道這個地址就是aData。

(5) 當(dāng)某個被分配的單元因?yàn)閐elete需要回收時,該單元并不會返回給進(jìn)程堆,而是返回給MemoryPool。返回時,MemoryPool能夠知道該單 元的起始地址。這時,MemoryPool開始遍歷其所維護(hù)的內(nèi)存塊鏈表,判斷該單元的起始地址是否落在某個內(nèi)存塊的地址范圍內(nèi)。如果不在所有內(nèi)存地址范 圍內(nèi),則這個被回收的單元不屬于這個MemoryPool;如果在某個內(nèi)存塊的地址范圍內(nèi),那么它會將這個剛剛回收的分配單元加到這個內(nèi)存塊的 MemoryBlock所維護(hù)的自由分配單元鏈表的頭部,同時將其nFree值遞增1。回收后,考慮到資源的有效利用及后續(xù)操作的性能,內(nèi)存池的操作會繼 續(xù)判斷:如果此內(nèi)存塊的所有分配單元都是自由的,那么這個內(nèi)存塊就會從MemoryPool中被移出并作為一個整體返回給進(jìn)程堆;如果該內(nèi)存塊中還有非自 由分配單元,這時不能將此內(nèi)存塊返回給進(jìn)程堆。但是因?yàn)閯倓傆幸粋€分配單元返回給了這個內(nèi)存塊,即這個內(nèi)存塊有自由分配單元可供下次分配,因此它會被移到 MemoryPool維護(hù)的內(nèi)存塊的頭部。這樣下次的內(nèi)存請求到來,MemoryPool遍歷其內(nèi)存塊鏈表以尋找自由分配單元時,第1次尋找就會找到這個 內(nèi)存塊。因?yàn)檫@個內(nèi)存塊確實(shí)有自由分配單元,這樣可以減少M(fèi)emoryPool的遍歷次數(shù)。

綜上所述,每個內(nèi) 存池(MemoryPool)維護(hù)一個內(nèi)存塊鏈表(單鏈表),每個內(nèi)存塊由一個維護(hù)該內(nèi)存塊信息的塊頭結(jié)構(gòu)(MemoryBlock)和多個分配單元組 成,塊頭結(jié)構(gòu)MemoryBlock則進(jìn)一步維護(hù)一個該內(nèi)存塊的所有自由分配單元組成的"鏈表"。這個鏈表不是通過"指向下一個自由分配單元的指針"鏈接 起來的,而是通過"下一個自由分配單元的編號"鏈接起來,這個編號值存儲在該自由分配單元的頭兩個字節(jié)中。另外,第1個自由分配單元的起始位置并不是 MemoryBlock結(jié)構(gòu)體"后面的"第1個地址位置,而是MemoryBlock結(jié)構(gòu)體"內(nèi)部"的最后一個字節(jié)aData(也可能不是最后一個,因?yàn)? 考慮到字節(jié)對齊的問題),即分配單元實(shí)際上往前面錯了一位。又因?yàn)镸emoryBlock結(jié)構(gòu)體后面的空間剛好是分配單元的整數(shù)倍,這樣依次錯位下去,內(nèi) 存塊的最后一個字節(jié)實(shí)際沒有被利用。這么做的一個原因也是考慮到不同平臺的移植問題,因?yàn)椴煌脚_的對齊方式可能不盡相同。即當(dāng)申請 MemoryBlock大小內(nèi)存時,可能會返回比其所有成員大小總和還要大一些的內(nèi)存。最后的幾個字節(jié)是為了"補(bǔ)齊",而使得aData成為第1個分配單 元的起始位置,這樣在對齊方式不同的各種平臺上都可以工作。

6.2.3 細(xì)節(jié)剖析

有了上述的總體印象后,本節(jié)來仔細(xì)剖析其實(shí)現(xiàn)細(xì)節(jié)。

(1)MemoryPool的構(gòu)造如下:


MemoryPool::MemoryPool( USHORT _nUnitSize,
USHORT _nInitSize, USHORT _nGrowSize )
{
pBlock = NULL; ①
nInitSize = _nInitSize; ②
nGrowSize = _nGrowSize; ③

if ( _nUnitSize > 4 )
nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); ④
else if ( _nUnitSize <= 2 )
nUnitSize = 2; ⑤
else
nUnitSize = 4;
}

從①處可以看出,MemoryPool創(chuàng)建時,并沒有立刻創(chuàng)建真正用來滿足內(nèi)存申請的內(nèi)存塊,即內(nèi)存塊鏈表剛開始時為空。

②處和③處分別設(shè)置"第1次創(chuàng)建的內(nèi)存塊所包含的分配單元的個數(shù)",及"隨后創(chuàng)建的內(nèi)存塊所包含的分配單元的個數(shù)",這兩個值在MemoryPool創(chuàng)建時通過參數(shù)指定,其后在該MemoryPool對象生命周期中一直不變。

后 面的代碼用來設(shè)置nUnitSize,這個值參考傳入的_nUnitSize參數(shù)。但是還需要考慮兩個因素。如前所述,每個分配單元在自由狀態(tài)時,其頭兩 個字節(jié)用來存放"其下一個自由分配單元的編號"。即每個分配單元"最少"有"兩個字節(jié)",這就是⑤處賦值的原因。④處是將大于4個字節(jié)的大小 _nUnitSize往上"取整到"大于_nUnitSize的最小的MEMPOOL_ ALIGNMENT的倍數(shù)(前提是MEMPOOL_ALIGNMENT為2的倍數(shù))。如_nUnitSize為11 時,MEMPOOL_ALIGNMENT為8,nUnitSize為16;MEMPOOL_ALIGNMENT為4,nUnitSize為 12;MEMPOOL_ALIGNMENT為2,nUnitSize為12,依次類推。

(2)當(dāng)向MemoryPool提出內(nèi)存請求時:


void* MemoryPool::Alloc()
{
if ( !pBlock ) ①
{
……
}

MemoryBlock* pMyBlock = pBlock;
while (pMyBlock && !pMyBlock->nFree )②
pMyBlock = pMyBlock->pNext;

if ( pMyBlock ) ③
{
char* pFree = pMyBlock->aData+(pMyBlock->nFirst*nUnitSize);
pMyBlock->nFirst = *((USHORT*)pFree);

pMyBlock->nFree--;
return (void*)pFree;
}
else ④
{
if ( !nGrowSize )
return NULL;

pMyBlock = new(nGrowSize, nUnitSize) FixedMemBlock(nGrowSize, nUnitSize);
if ( !pMyBlock )
return NULL;

pMyBlock->pNext = pBlock;
pBlock = pMyBlock;

return (void*)(pMyBlock->aData);
}

}

MemoryPool滿足內(nèi)存請求的步驟主要由四步組成。

① 處首先判斷內(nèi)存池當(dāng)前內(nèi)存塊鏈表是否為空,如果為空,則意味著這是第1次內(nèi)存申請請求。這時,從進(jìn)程堆中申請一個分配單元個數(shù)為nInitSize的內(nèi)存 塊,并初始化該內(nèi)存塊(主要初始化MemoryBlock結(jié)構(gòu)體成員,以及創(chuàng)建初始的自由分配單元鏈表,下面會詳細(xì)分析其代碼)。如果該內(nèi)存塊申請成功, 并初始化完畢,返回第1個分配單元給調(diào)用函數(shù)。第1個分配單元以MemoryBlock結(jié)構(gòu)體內(nèi)的最后一個字節(jié)為起始地址。

②處的作用是當(dāng)內(nèi)存池中已有內(nèi)存塊(即內(nèi)存塊鏈表不為空)時遍歷該內(nèi)存塊鏈表,尋找還有"自由分配單元"的內(nèi)存塊。

③ 處檢查如果找到還有自由分配單元的內(nèi)存塊,則"定位"到該內(nèi)存塊現(xiàn)在可以用的自由分配單元處。"定位"以MemoryBlock結(jié)構(gòu)體內(nèi)的最后一個字節(jié)位 置aData為起始位置,以MemoryPool的nUnitSize為步長來進(jìn)行。找到后,需要修改MemoryBlock的nFree信息(剩下來的 自由分配單元比原來減少了一個),以及修改此內(nèi)存塊的自由存儲單元鏈表的信息。在找到的內(nèi)存塊中,pMyBlock->nFirst為該內(nèi)存塊中自 由存儲單元鏈表的表頭,其下一個自由存儲單元的編號存放在pMyBlock->nFirst指示的自由存儲單元(亦即剛才定位到的自由存儲單元)的 頭兩個字節(jié)。通過剛才定位到的位置,取其頭兩個字節(jié)的值,賦給pMyBlock->nFirst,這就是此內(nèi)存塊的自由存儲單元鏈表的新的表頭,即 下一次分配出去的自由分配單元的編號(如果nFree大于零的話)。修改維護(hù)信息后,就可以將剛才定位到的自由分配單元的地址返回給此次申請的調(diào)用函數(shù)。 注意,因?yàn)檫@個分配單元已經(jīng)被分配,而內(nèi)存塊無須維護(hù)已分配的分配單元,因此該分配單元的頭兩個字節(jié)的信息已經(jīng)沒有用處。換個角度看,這個自由分配單元返 回給調(diào)用函數(shù)后,調(diào)用函數(shù)如何處置這塊內(nèi)存,內(nèi)存池?zé)o從知曉,也無須知曉。此分配單元在返回給調(diào)用函數(shù)時,其內(nèi)容對于調(diào)用函數(shù)來說是無意義的。因此幾乎可 以肯定調(diào)用函數(shù)在用這個單元的內(nèi)存時會覆蓋其原來的內(nèi)容,即頭兩個字節(jié)的內(nèi)容也會被抹去。因此每個存儲單元并沒有因?yàn)樾枰溄佣攵嘤嗟木S護(hù)信息,而是 直接利用單元內(nèi)的頭兩個字節(jié),當(dāng)其分配后,頭兩個字節(jié)也可以被調(diào)用函數(shù)利用。而在自由狀態(tài)時,則用來存放維護(hù)信息,即下一個自由分配單元的編號,這是一個 有效利用內(nèi)存的好例子。

④處表示在②處遍歷時,沒有找到還有自由分配單元的內(nèi)存塊,這時,需要重新向進(jìn)程堆申 請一個內(nèi)存塊。因?yàn)椴皇堑谝淮紊暾垉?nèi)存塊,所以申請的內(nèi)存塊包含的分配單元個數(shù)為nGrowSize,而不再是nInitSize。與①處相同,先做這個 新申請內(nèi)存塊的初始化工作,然后將此內(nèi)存塊插入MemoryPool的內(nèi)存塊鏈表的頭部,再將此內(nèi)存塊的第1個分配單元返回給調(diào)用函數(shù)。將此新內(nèi)存塊插入 內(nèi)存塊鏈表的頭部的原因是該內(nèi)存塊還有很多可供分配的自由分配單元(除非nGrowSize等于1,這應(yīng)該不太可能。因?yàn)閮?nèi)存池的含義就是一次性地從進(jìn)程 堆中申請一大塊內(nèi)存,以供后續(xù)的多次申請),放在頭部可以使得在下次收到內(nèi)存申請時,減少②處對內(nèi)存塊的遍歷時間。

可以用圖6-2的MemoryPool來展示MemoryPool::Alloc的過程。圖6-3是某個時刻MemoryPool的內(nèi)部狀態(tài)。


圖6-3 某個時刻MemoryPool的內(nèi)部狀態(tài)
圖6-3  某個時刻memorypool的內(nèi)部狀態(tài)

因 為MemoryPool的內(nèi)存塊鏈表不為空,因此會遍歷其內(nèi)存塊鏈表。又因?yàn)榈?個內(nèi)存塊里有自由的分配單元,所以會從第1個內(nèi)存塊中分配。檢查 nFirst,其值為m,這時pBlock->aData+(pBlock->nFirst*nUnitSize)定位到編號為m的自由分配 單元的起始位置(用pFree表示)。在返回pFree之前,需要修改此內(nèi)存塊的維護(hù)信息。首先將nFree遞減1,然后取得pFree處開始的頭兩個字 節(jié)的值(需要說明的是,這里aData處值為k。其實(shí)不是這一個字節(jié)。而是以aData和緊跟其后的另外一個字節(jié)合在一起構(gòu)成的一個USHORT的值,不 可誤會)。發(fā)現(xiàn)為k,這時修改pBlock的nFirst為k。然后,返回pFree。此時MemoryPool的結(jié)構(gòu)如圖6-4所示。


圖6-4 MemoryPool的結(jié)構(gòu)
圖6-4  memorypool的結(jié)構(gòu)

可以看到,原來的第1個可供分配的單元(m編號處)已經(jīng)顯示為被分配的狀態(tài)。而pBlock的nFirst已經(jīng)指向原來m單元下一個自由分配單元的編號,即k。

(3)MemoryPool回收內(nèi)存時:


void MemoryPool::Free( void* pFree )
{
……

MemoryBlock* pMyBlock = pBlock;

while ( ((ULONG)pMyBlock->aData > (ULONG)pFree) ||
((ULONG)pFree >= ((ULONG)pMyBlock->aData + pMyBlock->nSize)) )①
{
……
}

pMyBlock->nFree++; ②
*((USHORT*)pFree) = pMyBlock->nFirst; ③
pMyBlock->nFirst = (USHORT)(((ULONG)pFree-(ULONG)(pBlock->aData)) / nUnitSize);④

if (pMyBlock->nFree*nUnitSize == pMyBlock->nSize )⑤
{
……
}
else
{
……
}
}

如前所述,回收分配單元時,可能會將整個內(nèi)存塊返回給進(jìn)程堆,也可能將被回收分配單元所屬的內(nèi)存塊移至內(nèi)存池的內(nèi)存塊鏈表的頭部。這兩個操作都需要修改鏈表結(jié)構(gòu)。這時需要知道該內(nèi)存塊在鏈表中前一個位置的內(nèi)存塊。

①處遍歷內(nèi)存池的內(nèi)存塊鏈表,確定該待回收分配單元(pFree)落在哪一個內(nèi)存塊的指針范圍內(nèi),通過比較指針值來確定。

運(yùn) 行到②處,pMyBlock即找到的包含pFree所指向的待回收分配單元的內(nèi)存塊(當(dāng)然,這時應(yīng)該還需要檢查pMyBlock為NULL時的情形,即 pFree不屬于此內(nèi)存池的范圍,因此不能返回給此內(nèi)存池,讀者可以自行加上)。這時將pMyBlock的nFree遞增1,表示此內(nèi)存塊的自由分配單元 多了一個。

③處用來修改該內(nèi)存塊的自由分配單元鏈表的信息,它將這個待回收分配單元的頭兩個字節(jié)的值指向該內(nèi)存塊原來的第一個可分配的自由分配單元的編號。

④處將pMyBlock的nFirst值改變?yōu)橹赶蜻@個待回收分配單元的編號,其編號通過計(jì)算此單元的起始位置相對pMyBlock的aData位置的差值,然后除以步長(nUnitSize)得到。

實(shí) 質(zhì)上,③和④兩步的作用就是將此待回收分配單元"真正回收"。值得注意的是,這兩步實(shí)際上是使得此回收單元成為此內(nèi)存塊的下一個可分配的自由分配單元,即 將它放在了自由分配單元鏈表的頭部。注意,其內(nèi)存地址并沒有發(fā)生改變。實(shí)際上,一個分配單元的內(nèi)存地址無論是在分配后,還是處于自由狀態(tài)時,一直都不會變 化。變化的只是其狀態(tài)(已分配/自由),以及當(dāng)其處于自由狀態(tài)時在自由分配單元鏈表中的位置。

⑤處檢查當(dāng)回收完畢后,包含此回收單元的內(nèi)存塊的所有單元是否都處于自由狀態(tài),且此內(nèi)存是否處于內(nèi)存塊鏈表的頭部。如果是,將此內(nèi)存塊整個的返回給進(jìn)程堆,同時修改內(nèi)存塊鏈表結(jié)構(gòu)。

注 意,這里在判斷一個內(nèi)存塊的所有單元是否都處于自由狀態(tài)時,并沒有遍歷其所有單元,而是判斷nFree乘以nUnitSize是否等于nSize。 nSize是內(nèi)存塊中所有分配單元的大小,而不包括頭部MemoryBlock結(jié)構(gòu)體的大小。這里可以看到其用意,即用來快速檢查某個內(nèi)存塊中所有分配單 元是否全部處于自由狀態(tài)。因?yàn)橹恍杞Y(jié)合nFree和nUnitSize來計(jì)算得出結(jié)論,而無須遍歷和計(jì)算所有自由狀態(tài)的分配單元的個數(shù)。

另 外還需注意的是,這里并不能比較nFree與nInitSize或nGrowSize的大小來判斷某個內(nèi)存塊中所有分配單元都為自由狀態(tài),這是因?yàn)榈?次 分配的內(nèi)存塊(分配單元個數(shù)為nInitSize)可能被移到鏈表的后面,甚至可能在移到鏈表后面后,因?yàn)槟硞€時間其所有單元都處于自由狀態(tài)而被整個返回 給進(jìn)程堆。即在回收分配單元時,無法判定某個內(nèi)存塊中的分配單元個數(shù)到底是nInitSize還是nGrowSize,也就無法通過比較nFree與 nInitSize或nGrowSize的大小來判斷一個內(nèi)存塊的所有分配單元是否都為自由狀態(tài)。

以上面分配后的內(nèi)存池狀態(tài)作為例子,假設(shè)這時第2個內(nèi)存塊中的最后一個單元需要回收(已被分配,假設(shè)其編號為m,pFree指針指向它),如圖6-5所示。

不 難發(fā)現(xiàn),這時nFirst的值由原來的0變?yōu)閙。即此內(nèi)存塊下一個被分配的單元是m編號的單元,而不是0編號的單元(最先分配的是最新回收的單元,從這一 點(diǎn)看,這個過程與棧的原理類似,即先進(jìn)后出。只不過這里的"進(jìn)"意味著"回收",而"出"則意味著"分配")。相應(yīng)地,m的"下一個自由單元"標(biāo)記為0, 即內(nèi)存塊原來的"下一個將被分配出去的單元",這也表明最近回收的分配單元被插到了內(nèi)存塊的"自由分配單元鏈表"的頭部。當(dāng)然,nFree遞增1。


圖6-5 分配后的內(nèi)存池狀態(tài)
圖6-5  分配后的內(nèi)存池狀態(tài)

處理至⑥處之前,其狀態(tài)如圖6-6所示。


圖6-6 處理至⑥處之前的內(nèi)存池狀態(tài)
圖6-6  處理至⑥處之前的內(nèi)存池狀態(tài)

這 里需要注意的是,雖然pFree被"回收",但是pFree仍然指向m編號的單元,這個單元在回收過程中,其頭兩個字節(jié)被覆寫,但其他部分的內(nèi)容并沒有改 變。而且從整個進(jìn)程的內(nèi)存使用角度來看,這個m編號的單元的狀態(tài)仍然是"有效的"。因?yàn)檫@里的"回收"只是回收給了內(nèi)存池,而并沒有回收給進(jìn)程堆,因此程 序仍然可以通過pFree訪問此單元。但是這是一個很危險的操作,因?yàn)槭紫仍搯卧诨厥者^程中頭兩個字節(jié)已被覆寫,并且該單元可能很快就會被內(nèi)存池重新分 配。因此回收后通過pFree指針對這個單元的訪問都是錯誤的,讀操作會讀到錯誤的數(shù)據(jù),寫操作則可能會破壞程序中其他地方的數(shù)據(jù),因此需要格外小心。

接著,需要判斷該內(nèi)存塊的內(nèi)部使用情況,及其在內(nèi)存塊鏈表中的位置。如果該內(nèi)存塊中省略號"……"所表示的其他部分中還有被分配的單元,即nFree乘以nUnitSize不等于nSize。因?yàn)榇藘?nèi)存塊不在鏈表頭,因此還需要將其移到鏈表頭部,如圖6-7所示。


圖6-7 因回收引起的MemoryBlock移動
圖6-7  因回收引起的memoryblock移動

如果該內(nèi)存塊中省略號"……"表示的其他部分中全部都是自由分配單元,即nFree乘以nUnitSize等于nSize。因?yàn)榇藘?nèi)存塊不在鏈表頭,所以此時需要將此內(nèi)存塊整個回收給進(jìn)程堆,回收后內(nèi)存池的結(jié)構(gòu)如圖6-8所示。


圖6-8 回收后內(nèi)存池的結(jié)構(gòu)
圖6-8  回收后內(nèi)存池的結(jié)構(gòu)

一個內(nèi)存塊在申請后會初始化,主要是為了建立最初的自由分配單元鏈表,下面是其詳細(xì)代碼:


MemoryBlock::MemoryBlock (USHORT nTypes, USHORT nUnitSize)
: nSize (nTypes * nUnitSize),
nFree (nTypes - 1), ④
nFirst (1), ⑤
pNext (0)
{
char * pData = aData; ①
for (USHORT i = 1; i < nTypes; i++) ②
{
*reinterpret_cast<USHORT*>(pData) = i; ③
pData += nUnitSize;
}
}

這里可以看到,①處pData的初值是 aData,即0編號單元。但是②處的循環(huán)中i卻是從1開始,然后在循環(huán)內(nèi)部的③處將pData的頭兩個字節(jié)值置為i。即0號單元的頭兩個字節(jié)值為1,1 號單元的頭兩個字節(jié)值為2,一直到(nTypes-2)號單元的頭兩個字節(jié)值為(nTypes-1)。這意味著內(nèi)存塊初始時,其自由分配單元鏈表是從0號 開始。依次串聯(lián),一直到倒數(shù)第2個單元指向最后一個單元。

還需要注意的是,在其初始化列表中,nFree初始 化為nTypes-1(而不是nTypes),nFirst初始化為1(而不是0)。這是因?yàn)榈?個單元,即0編號單元構(gòu)造完畢后,立刻會被分配。另外注 意到最后一個單元初始并沒有設(shè)置頭兩個字節(jié)的值,因?yàn)樵搯卧跏荚诒緝?nèi)存塊中并沒有下一個自由分配單元。但是從上面例子中可以看到,當(dāng)最后一個單元被分配 并回收后,其頭兩個字節(jié)會被設(shè)置。

圖6-9所示為一個內(nèi)存塊初始化后的狀態(tài)。


圖6-9 一個內(nèi)存塊初始化后的狀態(tài)
圖6-9  一個內(nèi)存塊初始化后的狀態(tài)

當(dāng)內(nèi)存池析構(gòu)時,需要將內(nèi)存池的所有內(nèi)存塊返回給進(jìn)程堆:


MemoryPool::~MemoryPool()
{
MemoryBlock* pMyBlock = pBlock;
while ( pMyBlock )
{
……
}
}

6.2.4 使用方法

分 析內(nèi)存池的內(nèi)部原理后,本節(jié)說明如何使用它。從上面的分析可以看到,該內(nèi)存池主要有兩個對外接口函數(shù),即Alloc和Free。Alloc返回所申請的分 配單元(固定大小內(nèi)存),F(xiàn)ree則回收傳入的指針代表的分配單元的內(nèi)存給內(nèi)存池。分配的信息則通過MemoryPool的構(gòu)造函數(shù)指定,包括分配單元大 小、內(nèi)存池第1次申請的內(nèi)存塊中所含分配單元的個數(shù),以及內(nèi)存池后續(xù)申請的內(nèi)存塊所含分配單元的個數(shù)等。

綜上 所述,當(dāng)需要提高某些關(guān)鍵類對象的申請/回收效率時,可以考慮將該類所有生成對象所需的空間都從某個這樣的內(nèi)存池中開辟。在銷毀對象時,只需要返回給該內(nèi) 存池。"一個類的所有對象都分配在同一個內(nèi)存池對象中"這一需求很自然的設(shè)計(jì)方法就是為這樣的類聲明一個靜態(tài)內(nèi)存池對象,同時為了讓其所有對象都從這個內(nèi) 存池中開辟內(nèi)存,而不是缺省的從進(jìn)程堆中獲得,需要為該類重載一個new運(yùn)算符。因?yàn)橄鄳?yīng)地,回收也是面向內(nèi)存池,而不是進(jìn)程的缺省堆,還需要重載一個 delete運(yùn)算符。在new運(yùn)算符中用內(nèi)存池的Alloc函數(shù)滿足所有該類對象的內(nèi)存請求,而銷毀某對象則可以通過在delete運(yùn)算符中調(diào)用內(nèi)存池的 Free完成。

6.2.5 性能比較

為 了測試?yán)脙?nèi)存池后的效果,通過一個很小的測試程序可以發(fā)現(xiàn)采用內(nèi)存池機(jī)制后耗時為297 ms。而沒有采用內(nèi)存池機(jī)制則耗時625 ms,速度提高了52.48%。速度提高的原因可以歸結(jié)為幾點(diǎn),其一,除了偶爾的內(nèi)存申請和銷毀會導(dǎo)致從進(jìn)程堆中分配和銷毀內(nèi)存塊外,絕大多數(shù)的內(nèi)存申請 和銷毀都由內(nèi)存池在已經(jīng)申請到的內(nèi)存塊中進(jìn)行,而沒有直接與進(jìn)程堆打交道,而直接與進(jìn)程堆打交道是很耗時的操作;其二,這是單線程環(huán)境的內(nèi)存池,可以看到 內(nèi)存池的Alloc和Free操作中并沒有加線程保護(hù)措施。因此如果類A用到該內(nèi)存池,則所有類A對象的創(chuàng)建和銷毀都必須發(fā)生在同一個線程中。但如果類A 用到內(nèi)存池,類B也用到內(nèi)存池,那么類A的使用線程可以不必與類B的使用線程是同一個線程。

另外,在第1章中已經(jīng)討論過,因?yàn)閮?nèi)存池技術(shù)使得同類型的對象分布在相鄰的內(nèi)存區(qū)域,而程序會經(jīng)常對同一類型的對象進(jìn)行遍歷操作。因此在程序運(yùn)行過程中發(fā)生的缺頁應(yīng)該會相應(yīng)少一些,但這個一般只能在真實(shí)的復(fù)雜應(yīng)用環(huán)境中進(jìn)行驗(yàn)證。





回頁首


6.3 本章小結(jié)

內(nèi) 存的申請和釋放對一個應(yīng)用程序的整體性能影響極大,甚至在很多時候成為某個應(yīng)用程序的瓶頸。消除內(nèi)存申請和釋放引起的瓶頸的方法往往是針對內(nèi)存使用的實(shí)際 情況提供一個合適的內(nèi)存池。內(nèi)存池之所以能夠提高性能,主要是因?yàn)樗軌蚶脩?yīng)用程序的實(shí)際內(nèi)存使用場景中的某些"特性"。比如某些內(nèi)存申請與釋放肯定發(fā) 生在一個線程中,某種類型的對象生成和銷毀與應(yīng)用程序中的其他類型對象要頻繁得多,等等。針對這些特性,可以為這些特殊的內(nèi)存使用場景提供量身定做的內(nèi)存 池。這樣能夠消除系統(tǒng)提供的缺省內(nèi)存機(jī)制中,對于該實(shí)際應(yīng)用場景中的不必要的操作,從而提升應(yīng)用程序的整體性能。



作者簡介


馮 宏華,清華大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士。IBM 中國開發(fā)中心高級軟件工程師。 2003 年 12 月加入 IBM 中國開發(fā)中心,主要從事 IBM 產(chǎn)品的開發(fā)、性能優(yōu)化等工作。興趣包括 C/C++ 應(yīng)用程序性能調(diào)優(yōu),Windows 應(yīng)用程序開發(fā),Web 應(yīng)用程序開發(fā)等。



徐 瑩,山東大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士。2003 年 4 月加入 IBM 中國開發(fā)中心,現(xiàn)任 IBM 中國開發(fā)中心開發(fā)經(jīng)理,一直從事IBM軟件產(chǎn)品在多個操作系統(tǒng)平臺上的開發(fā)工作。曾參與 IBM 產(chǎn)品在 Windows 和 Linux 平臺上的性能優(yōu)化工作,對 C/C++ 編程語言和跨平臺的大型軟件系統(tǒng)的開發(fā)有較豐富的經(jīng)驗(yàn)。



程 遠(yuǎn),北京大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士。IBM 中國開發(fā)中心高級軟件工程師。2003 年加入 IBM 中國開發(fā)中心,主要從事IBM Productivity Tools 產(chǎn)品的開發(fā)、性能優(yōu)化等工作。興趣包括 C/C++ 編程語言,軟件性能工程,Windows/Linux 平臺性能測試優(yōu)化工具等。



汪 磊,北京航空航天大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士,目前是 IBM 中國軟件開發(fā)中心高級軟件工程師。從 2002 年 12 月加入 IBM 中國開發(fā)中心至今一直從事旨在提高企業(yè)生產(chǎn)效率的應(yīng)用軟件開發(fā)。興趣包括 C\C++ 應(yīng)用程序的性能調(diào)優(yōu),Java 應(yīng)用程序的性能調(diào)優(yōu)。





]]>
編輯decorate程序時遇到一個問題http://m.shnenglu.com/michaelgao/archive/2008/10/16/64170.htmlmicheal's techmicheal's techThu, 16 Oct 2008 09:04:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/10/16/64170.htmlhttp://m.shnenglu.com/michaelgao/comments/64170.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/10/16/64170.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/64170.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/64170.html{
    public:
        Decorator(Beverage * com);
        virtual ~Decorator();
        virtual string get_descrption();
    protected:
        Beverage * component;


};

而MilkDecorator繼承了Decorator,如果component 為私有的則MilkDecorator便不能訪問。

如果milkDecorator 設(shè)計(jì)成這樣就不會違反了封裝的原則。
基本上只有一個區(qū)別,就是protect成員能被派生類訪問!而派生類對private沒有特殊訪問權(quán)!



]]>
為什么C++編譯器不能支持對模板的分離式編譯 http://m.shnenglu.com/michaelgao/archive/2008/10/09/63571.htmlmicheal's techmicheal's techThu, 09 Oct 2008 09:23:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/10/09/63571.htmlhttp://m.shnenglu.com/michaelgao/comments/63571.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/10/09/63571.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/63571.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/63571.html為什么C++編譯器不能支持對模板的分離式編譯
劉未鵬(pongba) /文

首先,C++標(biāo)準(zhǔn)中提到,一個編譯單元[translation unit]是指一個.cpp文件以及它所include的所有.h文件,.h文件里的代碼將會被擴(kuò)展到包含它的.cpp文件里,然后編譯器編譯該.cpp 文件為一個.obj文件,后者擁有PE[Portable Executable,即windows可執(zhí)行文件]文件格式,并且本身包含的就已經(jīng)是二進(jìn)制碼,但是,不一定能夠執(zhí)行,因?yàn)椴⒉槐WC其中一定有main 函數(shù)。當(dāng)編譯器將一個工程里的所有.cpp文件以分離的方式編譯完畢后,再由連接器(linker)進(jìn)行連接成為一個.exe文件。
舉個例子:
//---------------test.h-------------------//
void f();//這里聲明一個函數(shù)f
//---------------test.cpp--------------//
#i nclude”test.h”
void f()
{
…//do something
} //這里實(shí)現(xiàn)出test.h中聲明的f函數(shù)
//---------------main.cpp--------------//
#i nclude”test.h”
int main()
{
f(); //調(diào)用f,f具有外部連接類型
}
在 這個例子中,test. cpp和main.cpp各被編譯成為不同的.obj文件[姑且命名為test.obj和main.obj],在main.cpp中,調(diào)用了f函數(shù),然而 當(dāng)編譯器編譯main.cpp時,它所僅僅知道的只是main.cpp中所包含的test.h文件中的一個關(guān)于void f();的聲明,所以,編譯器將這里的f看作外部連接類型,即認(rèn)為它的函數(shù)實(shí)現(xiàn)代碼在另一個.obj文件中,本例也就是test.obj,也就是 說,main.obj中實(shí)際沒有關(guān)于f函數(shù)的哪怕一行二進(jìn)制代碼,而這些代碼實(shí)際存在于test.cpp所編譯成的test.obj中。在 main.obj中對f的調(diào)用只會生成一行call指令,像這樣:
call f [C++中這個名字當(dāng)然是經(jīng)過mangling[處理]過的]
在 編譯時,這個call指令顯然是錯誤的,因?yàn)閙ain.obj中并無一行f的實(shí)現(xiàn)代碼。那怎么辦呢?這就是連接器的任務(wù),連接器負(fù)責(zé)在其它的.obj中 [本例為test.obj]尋找f的實(shí)現(xiàn)代碼,找到以后將call f這個指令的調(diào)用地址換成實(shí)際的f的函數(shù)進(jìn)入點(diǎn)地址。需要注意的是:連接器實(shí)際上將工程里的.obj“連接”成了一個.exe文件,而它最關(guān)鍵的任務(wù)就是 上面說的,尋找一個外部連接符號在另一個.obj中的地址,然后替換原來的“虛假”地址。
這個過程如果說的更深入就是:
call f這行指令其實(shí)并不是這樣的,它實(shí)際上是所謂的stub,也就是一個
jmp 0x23423[這個地址可能是任意的,然而關(guān)鍵是這個地址上有一行指令來進(jìn)行真正的call f動作。也就是說,這個.obj文件里面所有對f的調(diào)用都jmp向同一個地址,在后者那兒才真正”call”f。這樣做的好處就是連接器修改地址時只要對 后者的call XXX地址作改動就行了。但是,連接器是如何找到f的實(shí)際地址的呢[在本例中這處于test.obj中],因?yàn)?obj于.exe的格式都是一樣的,在這 樣的文件中有一個符號導(dǎo)入表和符號導(dǎo)出表[import table和export table]其中將所有符號和它們的地址關(guān)聯(lián)起來。這樣連接器只要在test.obj的符號導(dǎo)出表中尋找符號f[當(dāng)然C++對f作了mangling]的 地址就行了,然后作一些偏移量處理后[因?yàn)槭菍蓚€.obj文件合并,當(dāng)然地址會有一定的偏移,這個連接器清楚]寫入main.obj中的符號導(dǎo)入表中f 所占有的那一項(xiàng)。
這就是大概的過程。其中關(guān)鍵就是:
編譯main.cpp時,編譯器不知道f的實(shí)現(xiàn),所有當(dāng)碰到對它的調(diào)用時只是給出一個指示,指示連接器應(yīng)該為它尋找f的實(shí)現(xiàn)體。這也就是說main.obj中沒有關(guān)于f的任何一行二進(jìn)制代碼。
編譯test.cpp時,編譯器找到了f的實(shí)現(xiàn)。于是乎f的實(shí)現(xiàn)[二進(jìn)制代碼]出現(xiàn)在test.obj里。
連接時,連接器在test.obj中找到f的實(shí)現(xiàn)代碼[二進(jìn)制]的地址[通過符號導(dǎo)出表]。然后將main.obj中懸而未決的call XXX地址改成f實(shí)際的地址。
完成。

然而,對于模板,你知道,模板函數(shù)的代碼其實(shí)并不能直接編譯成二進(jìn)制代碼,其中要有一個“具現(xiàn)化”的過程。舉個例子:
//----------main.cpp------//
template<class T>
void f(T t)
{}
int main()
{
…//do something
f(10); //call f<int> 編譯器在這里決定給f一個f<int>的具現(xiàn)體
…//do other thing
}
也就是說,如果你在main.cpp文件中沒有調(diào)用過f,f也就得不到具現(xiàn),從而main.obj中也就沒有關(guān)于f的任意一行二進(jìn)制代碼!!如果你這樣調(diào)用了:
f(10); //f<int>得以具現(xiàn)化出來
f(10.0); //f<double>得以具現(xiàn)化出來
這樣main.obj中也就有了f<int>,f<double>兩個函數(shù)的二進(jìn)制代碼段。以此類推。
然而具現(xiàn)化要求編譯器知道模板的定義,不是嗎?
看下面的例子:[將模板和它的實(shí)現(xiàn)分離]
//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //這里只是個聲明
};
//---------------test.cpp-------------//
#i nclude”test.h”
template<class T>
void A<T>::f() //模板的實(shí)現(xiàn),但注意:不是具現(xiàn)
{
…//do something
}
//---------------main.cpp---------------//
#i nclude”test.h”
int main()
{
A<int> a;
a. f(); //編譯器在這里并不知道A<int>::f的定義,因?yàn)樗辉趖est.h里面
//于是編譯器只好寄希望于連接器,希望它能夠在其他.obj里面找到
//A<int>::f的實(shí)現(xiàn)體,在本例中就是test.obj,然而,后者中真有A<int>::f的
//二進(jìn)制代碼嗎?NO!!!因?yàn)镃++標(biāo)準(zhǔn)明確表示,當(dāng)一個模板不被用到的時
//侯它就不該被具現(xiàn)出來,test.cpp中用到了A<int>::f了嗎?沒有!!所以實(shí)
//際上test.cpp編譯出來的test.obj文件中關(guān)于A::f的一行二進(jìn)制代碼也沒有
//于是連接器就傻眼了,只好給出一個連接錯誤
// 但是,如果在test.cpp中寫一個函數(shù),其中調(diào)用A<int>::f,則編譯器會將其//具現(xiàn)出來,因?yàn)樵谶@個點(diǎn)上[test.cpp 中],編譯器知道模板的定義,所以能//夠具現(xiàn)化,于是,test.obj的符號導(dǎo)出表中就有了A<int>::f這個符號的地
//址,于是連接器就能夠完成任務(wù)。
}

關(guān)鍵是:在分離式編譯的環(huán)境下,編譯器編譯某一個.cpp文件時并不知道另一個.cpp文件的存在,也不會去查找[當(dāng)遇到未決符號時它會寄希望于連 接器]。這種模式在沒有模板的情況下運(yùn)行良好,但遇到模板時就傻眼了,因?yàn)槟0鍍H在需要的時候才會具現(xiàn)化出來,所以,當(dāng)編譯器只看到模板的聲明時,它不能 具現(xiàn)化該模板,只能創(chuàng)建一個具有外部連接的符號并期待連接器能夠?qū)⒎柕牡刂窙Q議出來。然而當(dāng)實(shí)現(xiàn)該模板的.cpp文件中沒有用到模板的具現(xiàn)體時,編譯器 懶得去具現(xiàn),所以,整個工程的.obj中就找不到一行模板具現(xiàn)體的二進(jìn)制代碼,于是連接器也黔

/////////////////////////////////
http://dev.csdn.net/develop/article/19/19587.shtm
 C++模板代碼的組織方式 ——包含模式(Inclusion Model)     選擇自 sam1111 的 Blog 
關(guān)鍵字   Template Inclusion Model
出處   C++ Template: The Complete Guide


說明:本文譯自《C++ Template: The Complete Guide》一書的第6章中的部分內(nèi)容。最近看到C++論壇上常有關(guān)于模板的包含模式的帖子,聯(lián)想到自己初學(xué)模板時,也為類似的問題困惑過,因此翻譯此文,希望對初學(xué)者有所幫助。

模板代碼有幾種不同的組織方式,本文介紹其中最流行的一種方式:包含模式。

鏈接錯誤

大多數(shù)C/C++程序員向下面這樣組織他們的非模板代碼:

         ·類和其他類型全部放在頭文件中,這些頭文件具有.hpp(或者.H, .h, .hh, .hxx)擴(kuò)展名。

         ·對于全局變量和(非內(nèi)聯(lián))函數(shù),只有聲明放在頭文件中,而定義放在點(diǎn)C文件中,這些文件具有.cpp(或者.C, .c, .cc, .cxx)擴(kuò)展名。
 

這種組織方式工作的很好:它使得在編程時可以方便地訪問所需的類型定義,并且避免了來自鏈接器的“變量或函數(shù)重復(fù)定義”的錯誤。
 

由于以上組織方式約定的影響,模板編程新手往往會犯一個同樣的錯誤。下面這一小段程序反映了這種錯誤。就像對待“普通代碼”那樣,我們在頭文件中定義模板:
 

// basics/myfirst.hpp 

#ifndef MYFIRST_HPP
#define MYFIRST_HPP 

// declaration of template

template <typename T>

void print_typeof (T const&);

#endif // MYFIRST_HPP

 

print_typeof()聲明了一個簡單的輔助函數(shù)用來打印一些類型信息。函數(shù)的定義放在點(diǎn)C文件中:

// basics/myfirst.cpp

#i nclude <iostream>

#i nclude <typeinfo>

#i nclude "myfirst.hpp"
 

// implementation/definition of template

template <typename T>
void print_typeof (T const& x)
{

    std::cout << typeid(x).name() << std::endl;

}

 

這個例子使用typeid操作符來打印一個字符串,這個字符串描述了傳入的參數(shù)的類型信息。

最后,我們在另外一個點(diǎn)C文件中使用我們的模板,在這個文件中模板聲明被#i nclude:

// basics/myfirstmain.cpp 

#i nclude "myfirst.hpp" 

// use of the template

int main()
{

    double ice = 3.0;
    print_typeof(ice);  // call function template for type double

}

 
大部分C++編譯器(Compiler)很可能會接受這個程序,沒有任何問題,但是鏈接器(Linker)大概會報告一個錯誤,指出缺少函數(shù)print_typeof()的定義。

這個錯誤的原因在于,模板函數(shù)print_typeof()的定義還沒有被具現(xiàn)化(instantiate)。為了具現(xiàn)化一個模板,編譯器必須知道 哪一個定義應(yīng)該被具現(xiàn)化,以及使用什么樣的模板參數(shù)來具現(xiàn)化。不幸的是,在前面的例子中,這兩組信息存在于分開編譯的不同文件中。因此,當(dāng)我們的編譯器看 到對print_typeof()的調(diào)用,但是沒有看到此函數(shù)為double類型具現(xiàn)化的定義時,它只是假設(shè)這樣的定義在別處提供,并且創(chuàng)建一個那個定義 的引用(鏈接器使用此引用解析)。另一方面,當(dāng)編譯器處理myfirst.cpp時,該文件并沒有任何指示表明它必須為它所包含的特殊參數(shù)具現(xiàn)化模板定 義。

頭文件中的模板

解決上面這個問題的通用解法是,采用與我們使用宏或者內(nèi)聯(lián)函數(shù)相同的方法:我們將模板的定義包含進(jìn)聲明模板的頭文件中。對于我們的例子,我們可以通 過將#i nclude "myfirst.cpp"添加到myfirst.hpp文件尾部,或者在每一個使用我們的模板的點(diǎn)C文件中包含myfirst.cpp文件,來達(dá)到目 的。當(dāng)然,還有第三種方法,就是刪掉myfirst.cpp文件,并重寫myfirst.hpp文件,使它包含所有的模板聲明與定義:


// basics/myfirst2.hpp

#ifndef MYFIRST_HPP
#define MYFIRST_HPP 

#i nclude <iostream>
#i nclude <typeinfo>
 

// declaration of template
template <typename T>
void print_typeof (T const&); 

// implementation/definition of template
template <typename T>
void print_typeof (T const& x)
{

    std::cout << typeid(x).name() << std::endl;

}

#endif // MYFIRST_HPP

 

這種組織模板代碼的方式就稱作包含模式。經(jīng)過這樣的調(diào)整,你會發(fā)現(xiàn)我們的程序已經(jīng)能夠正確編譯、鏈接、執(zhí)行了。

從這個方法中我們可以得到一些觀察結(jié)果。最值得注意的一點(diǎn)是,這個方法在相當(dāng)程度上增加了包含myfirst.hpp的開銷。在這個例子中,這種開 銷并不是由模板定義自身的尺寸引起的,而是由這樣一個事實(shí)引起的,即我們必須包含我們的模板用到的頭文件,在這個例子中 是<iostream>和<typeinfo>。你會發(fā)現(xiàn)這最終導(dǎo)致了成千上萬行的代碼,因?yàn)橹T 如<iostream>這樣的頭文件也包含了和我們類似的模板定義。

這在實(shí)踐中確實(shí)是一個問題,因?yàn)樗黾恿司幾g器在編譯一個實(shí)際程序時所需的時間。我們因此會在以后的章節(jié)中驗(yàn)證其他一些可能的方法來解決這個問題。但無論如何,現(xiàn)實(shí)世界中的程序花一小時來編譯鏈接已經(jīng)是快的了(我們曾經(jīng)遇到過花費(fèi)數(shù)天時間來從源碼編譯的程序)。

拋開編譯時間不談,我們強(qiáng)烈建議如果可能盡量按照包含模式組織模板代碼。

另一個觀察結(jié)果是,非內(nèi)聯(lián)模板函數(shù)與內(nèi)聯(lián)函數(shù)和宏的最重要的不同在于:它并不會在調(diào)用端展開。相反,當(dāng)模板函數(shù)被具現(xiàn)化時,會產(chǎn)生此函數(shù)的一個新的 拷貝。由于這是一個自動的過程,編譯器也許會在不同的文件中產(chǎn)生兩個相同的拷貝,從而引起鏈接器報告一個錯誤。理論上,我們并不關(guān)心這一點(diǎn):這是編譯器設(shè) 計(jì)者應(yīng)當(dāng)關(guān)心的事情。實(shí)際上,大多數(shù)時候一切都運(yùn)轉(zhuǎn)正常,我們根本就不用處理這種狀況。然而,對于那些需要創(chuàng)建自己的庫的大型項(xiàng)目,這個問題偶爾會顯現(xiàn)出 來。
 

最后,需要指出的是,在我們的例子中,應(yīng)用于普通模板函數(shù)的方法同樣適用于模板類的成員函數(shù)和靜態(tài)數(shù)據(jù)成員,以及模板成員函數(shù)。





]]>
C++中接口與實(shí)現(xiàn)的分離句柄類http://m.shnenglu.com/michaelgao/archive/2008/10/07/63401.htmlmicheal's techmicheal's techTue, 07 Oct 2008 08:13:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/10/07/63401.htmlhttp://m.shnenglu.com/michaelgao/comments/63401.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/10/07/63401.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/63401.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/63401.html
句柄類是存儲和管理基類指針的一個類
需要句柄類的背景:
1)在對安全要求很高的領(lǐng)域,即使核心實(shí)現(xiàn)已經(jīng)封閉在庫中不可見,但頭文件中變量定義仍可能曝露一些內(nèi)部信息
2)在設(shè)計(jì)初期、實(shí)現(xiàn)部分會經(jīng)常變動,甚至頭文件中變量定義也需要經(jīng)常變動,因此在重編譯的時候頭文件也需要編譯,
有時候?qū)е戮幾g時間過長。
句柄類就是為了解決這類問題
// Handle.h
class Implement; //這里是對真正實(shí)現(xiàn)功能的類的聲明

class ImplementHandle //這是Implement類的句柄類
{
public:
ImplementHandle():lpImplementInstance(NULL){lpImplementInstance = new Implement;}
~ImplementHandle(){ delete lpImplementInstance ;}
public:
// Interface_FOO() 是句柄類提供的接口,它的真正實(shí)現(xiàn)是在Implement類里面,而這個僅僅是接口"代理"
RE_TYPE Interface_FOO()
{
return lpImplementInstance->FOO
_
Implementation(); //句柄代理調(diào)用實(shí)現(xiàn)真正的FOO接口
}
//還有其他的接口也一樣,均是用句柄類代理接口
//....
private:
Implement * lpImplementInstance; //這個是句柄類唯一的數(shù)據(jù)成員(當(dāng)然,并不一定),可以被句柄類很好地自動管理
};




  被封裝在句柄類里面的真正實(shí)現(xiàn)(class Implement)將與用戶隔離開來,就是說,就算以后Implement 類的實(shí)現(xiàn)有所改變,
只要它提供的接口不變,那么它的句柄類就不會改變,而包含句柄類的用戶,也不用做任何相應(yīng)的改變(所有包含 “Handle.h”的模塊甚至不用從新編譯就可以正常更新至最新的Implement實(shí)現(xiàn))。

例如
#include "Handle.h"
Imlementhandle testhandle;
testhandle->Interface_Foo();//調(diào)用接口即可。
如果改動了Implent類的方法

  于此相反,如果直接用class Implement 的定義,那么只要Implement類的定義有所改變(不管是public 還是private 成員
的更新),那么所有包含它的頭文件的模塊都要從新編譯一次。

這其實(shí)就是接口與實(shí)現(xiàn)的分離,




]]>
C++實(shí)現(xiàn)設(shè)計(jì)模式中的Interface from goolgehttp://m.shnenglu.com/michaelgao/archive/2008/10/07/63392.htmlmicheal's techmicheal's techTue, 07 Oct 2008 06:39:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/10/07/63392.htmlhttp://m.shnenglu.com/michaelgao/comments/63392.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/10/07/63392.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/63392.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/63392.htmlInterface suffix.
link

Definition:

A class is a pure interface if it meets the following requirements:

  • It has only public pure virtual ("= 0") methods and static methods (but see below for destructor).
  • It may not have non-static data members.
  • It need not have any constructors defined. If a constructor is provided, it must take no arguments and it must be protected.
  • If it is a subclass, it may only be derived from classes that satisfy these conditions and are tagged with the Interface suffix.

An interface class can never be directly instantiated because of the pure virtual method(s) it declares. To make sure all implementations of the interface can be destroyed correctly, they must also declare a virtual destructor (in an exception to the first rule, this should not be pure). See Stroustrup, The C++ Programming Language, 3rd edition, section 12.4 for details.

Pros: Tagging a class with the Interface suffix lets others know that they must not add implemented methods or non static data members. This is particularly important in the case of multiple inheritance. Additionally, the interface concept is already well-understood by Java programmers.

Cons: The Interface suffix lengthens the class name, which can make it harder to read and understand. Also, the interface property may be considered an implementation detail that shouldn't be exposed to clients.

Decision: A class may end with Interface only if it meets the above requirements. We do not require the converse, however: classes that meet the above requirements are not required to end with Interface.




]]>
virtual destruct (確定基類有虛析構(gòu)函數(shù))http://m.shnenglu.com/michaelgao/archive/2008/07/08/55646.htmlmicheal's techmicheal's techTue, 08 Jul 2008 09:10:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/07/08/55646.htmlhttp://m.shnenglu.com/michaelgao/comments/55646.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/07/08/55646.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/55646.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/55646.html
#include <iostream>
using namespace std;
class Base{
    private:
        int num1;
    public:
        Base():num1(10)
        {
        }
        virtual ~Base()
        {
            cout<<"base"<<endl;
        }
};
class Dervied:public Base{
    private:
        int num2;
    public:
        Dervied():num2(1)
        {
        }
        ~Dervied()
        {
            cout<<"Dervied"<<endl;
        }
};
int main()
{
    Base *pBase = new Dervied();
    delete pBase;
}

結(jié)果會
Dervied
Base
#include<iostream>
using namespace std;

class ClxBase
{
    public:
       ClxBase() {};
       ~ClxBase() {cout<<"Do base class destruct"<<endl;};

};

class ClxDerived : public ClxBase
{
    public:
       ClxDerived() {};
       ~ClxDerived() { cout << "Do derived class destruct!" << endl; };

};


int main()
{

    ClxBase *pTest = new ClxDerived;
    ClxDerived derived;

    delete pTest;

}
另一個例子輸出的卻是 :
Do base class destruct
Do derived class destruct!
Do base class destruct

說明了兩個問題,只想派生類對象的基類指針需要基類聲明虛系構(gòu)函數(shù)才能調(diào)用派生類指針,而派生類對象卻不用基類聲明虛系構(gòu)函數(shù)就能調(diào)用派生類指針




]]>
malloc free realloc重新重載http://m.shnenglu.com/michaelgao/archive/2008/06/06/52374.htmlmicheal's techmicheal's techFri, 06 Jun 2008 09:02:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/06/06/52374.htmlhttp://m.shnenglu.com/michaelgao/comments/52374.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/06/06/52374.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/52374.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/52374.htmlmalloc
free
realloc
函數(shù),dmalloc 這個便是其這個作用的。但是由于dbmalloc性能影響較大。可以采用輕量級的重載,在malloc free realloc填充字符。最終通過core dump文件可以看到原因。
方案措施開始想到用dlopen,dlsymbol,dlload這種方案,但是這種方案會重復(fù)調(diào)用。是不可能實(shí)現(xiàn)的,最終采用的只能是用宏來代替。基本的方案就是定義一個公用的頭文件,這個頭文件的宏發(fā)生了變化,然后每個調(diào)用malloc,free等的都要包含頭文件。當(dāng)然在引用頭文件的時候我們也要定義一個c/c++文件來重新實(shí)現(xiàn),他不需要包含這個頭文件。

在過程中遇到的問題
1、頭文件的包含順序,應(yīng)該放在后面才會重新定義。
2、C/C++混合,malloc等是C的函數(shù)。
3、realloc會重新改變位置,比較容易出錯的。
4、free(0)是可以的,要注意出錯。



]]>
C/C++頭文件http://m.shnenglu.com/michaelgao/archive/2008/06/05/52268.htmlmicheal's techmicheal's techThu, 05 Jun 2008 09:19:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52268.htmlhttp://m.shnenglu.com/michaelgao/comments/52268.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52268.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/52268.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/52268.html2、如果要修改功能僅僅需要簡單修改頭文件。




]]>
[導(dǎo)入]extern "C"用法http://m.shnenglu.com/michaelgao/archive/2008/06/05/52246.htmlmicheal's techmicheal's techThu, 05 Jun 2008 07:59:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52246.htmlhttp://m.shnenglu.com/michaelgao/comments/52246.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52246.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/52246.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/52246.html
extern "C" 鏈接指示符不能在函數(shù)體內(nèi)定義。
extern "Fortran"
等等。

extern"C"
為了混合聯(lián)編而出現(xiàn)的。
1

C++
中引用C的頭文件,然后包括librarydll動態(tài)和靜態(tài)的加載了。例如:
extern "C"
{
    #include  "Cheader.h"
}
 #pragment mylib
等。
/*
引用Cheader中的函數(shù)了*/
或者可以extern "C"的函數(shù)。

所以標(biāo)準(zhǔn)的頭文件中就會出現(xiàn):
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
這樣是為了使得C++引用頭文件不用再添加這個extern "C" {...}

2
C引用C++的函數(shù)的時候要注意,此時C++的頭文件應(yīng)該包含著extern "C",但是在C語言中不能直接引用聲明了extern "C"的該頭文件,應(yīng)該僅將C文件中將C++中定義的extern "C"函數(shù)聲明為extern類型。

michalegao 2008-06-05 14:42 發(fā)表評論

文章來源:http://www.cnblogs.com/michael-gao/archive/2008/06/05/1214470.html

]]>
[導(dǎo)入]new 表達(dá)式(placement new expression)http://m.shnenglu.com/michaelgao/archive/2008/06/05/52247.htmlmicheal's techmicheal's techThu, 05 Jun 2008 07:59:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52247.htmlhttp://m.shnenglu.com/michaelgao/comments/52247.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52247.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/52247.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/52247.html void placement() {

char *buf = new char[1000]; //pre-allocated buffer

string *p = new (buf) string("hi"); //placement new

string *q = new string("hi"); //ordinary heap allocation

cout<
<
c_str()
<
<c_str();

}

placement new 表達(dá)式只是定位,不存在與其相對應(yīng)的delete,如果delete則選擇
delete[] buf。


michalegao 2008-06-05 12:03 發(fā)表評論

文章來源:http://www.cnblogs.com/michael-gao/archive/2008/06/05/1214239.html

]]>
[導(dǎo)入]C++ new的實(shí)現(xiàn),與C的malloc的關(guān)系。http://m.shnenglu.com/michaelgao/archive/2008/06/05/52248.htmlmicheal's techmicheal's techThu, 05 Jun 2008 07:59:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52248.htmlhttp://m.shnenglu.com/michaelgao/comments/52248.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52248.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/52248.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/52248.html 1、調(diào)用 void * operator new(size_t size);
      表示其返回的是一個未經(jīng)處理(raw)的指針,指向未初始化的內(nèi)存。參數(shù)size_t確定分配多少內(nèi)存。你能增加額外的參數(shù)重載函數(shù)operator new,但是第一個參數(shù)類型必須是size_t
2、調(diào)用類的構(gòu)造函數(shù)。
在第一步,operator new是怎么申請內(nèi)存的? 是調(diào)用的 malloc來申請內(nèi)存嗎?

operator new和delete函數(shù)的實(shí)現(xiàn)

下劃線表示不一定準(zhǔn)確,需要重新確認(rèn)。

    operator new實(shí)際上總是以標(biāo)準(zhǔn)的C malloc()完成,雖然并沒有規(guī)定非得這么做不可。同樣,operator delete也總是以標(biāo)準(zhǔn)得C free()來實(shí)現(xiàn),不考慮異常處理的話他們類似下面的樣子:

     extern void* operator new( size_t size )
{
    if( size == 0 )
        size = 1;       // 這里保證像 new T[0] 這樣得語句也是可行的
   
         void *last_alloc;
         while( !(last_alloc = malloc( size )) )
         {
             if( _new_handler )
                 ( *_new_handler )();
             else
                 return 0;
         }
         return last_alloc;
}

     extern void operator delete( void *ptr )
{
         if(ptr)  // 從這里可以看出,刪除一個空指針是安全
             free( (char*)ptr );
}



 
new和malloc區(qū)別兩個  
   
  1   new是操作符  
      malloc是庫函數(shù)  
   
  2   new可以調(diào)用構(gòu)造函數(shù),malloc不可以  




michalegao 2008-06-05 11:43 發(fā)表評論

文章來源:http://www.cnblogs.com/michael-gao/archive/2008/06/05/1214226.html

]]>
[導(dǎo)入]C++面試題--來自網(wǎng)上。http://m.shnenglu.com/michaelgao/archive/2008/06/05/52249.htmlmicheal's techmicheal's techThu, 05 Jun 2008 07:59:00 GMThttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52249.htmlhttp://m.shnenglu.com/michaelgao/comments/52249.htmlhttp://m.shnenglu.com/michaelgao/archive/2008/06/05/52249.html#Feedback0http://m.shnenglu.com/michaelgao/comments/commentRss/52249.htmlhttp://m.shnenglu.com/michaelgao/services/trackbacks/52249.html a. new 是 C++ 中的東西,而 malloc 是 C 中的東東
b. new 是操作符,而 malloc 是函數(shù)(?不記得是函數(shù)還是宏了)
c. new 可以對變量初始化,調(diào)用構(gòu)造函數(shù),而 malloc 沒有這個功能
d. new 是異常安全的,分配失敗可以捕獲到 std::bad_alloc 異常

2、ASSERT和VERIFY有什么區(qū)別;
a. ASSERT 宏的作用在于檢查表達(dá)式是否為假或?yàn)?NULL,如果為假則會引發(fā)異常,ASSERT 宏只在調(diào)試版本中才會有作用
b. VERIFY 宏與 ASSERT 宏的 VERIFY 的不同在與 VERIFY 在發(fā)行版本中同樣會起作用,但是使用 VERIFY 會導(dǎo)致非常不友好的用戶界面

3、模式對話框與非模式對話框有什么區(qū)別;
a. 模式對話框總是獨(dú)占的,而非模式對話框不是獨(dú)占的

4、SendMessage()與PostMessage()有什么區(qū)別;
a. SendMessage() 會等到返回才往下走,而 PostMessage 則不管

5、在繼承類中,子類是如何構(gòu)造的?又是如何析構(gòu)的?
a. 子類構(gòu)造:先調(diào)用基類的構(gòu)造函數(shù)(按繼續(xù)表順序),然后調(diào)用類成員的構(gòu)造函數(shù),最后調(diào)用執(zhí)行自己的構(gòu)造函數(shù)
   析構(gòu)通常情況下是相反的

6、什么是虛函數(shù)?
在 C++ 中,用 virtual 標(biāo)識的函數(shù)

7、什么是多態(tài)?
多態(tài)指發(fā)出同樣的消息被不同類型的對象接收時導(dǎo)致完全不同的行為

8、socket編程,如何處理阻塞?
a. 設(shè)置超時時間

9、靜態(tài)變量的作用是什么?靜態(tài)成員變量有什么優(yōu)缺點(diǎn)?
a. 控制存儲方式
b. 控制可見性與連接類型

michalegao 2008-06-05 10:55 發(fā)表評論

文章來源:http://www.cnblogs.com/michael-gao/archive/2008/06/05/1214180.html

]]>
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            免费成人性网站| 亚洲第一视频| 国产精品进线69影院| 欧美制服丝袜第一页| 久久er精品视频| 久久精品盗摄| 欧美激情久久久久| 国产精品久久久久久久免费软件| 国产精品国产| 国产午夜精品美女视频明星a级| 国内精品久久久久久久影视麻豆| 在线看成人片| 亚洲午夜久久久| 久久免费国产精品| 亚洲国产精品va在线看黑人| 欧美激情一区二区三区蜜桃视频 | 亚洲愉拍自拍另类高清精品| 欧美一区不卡| 亚洲第一主播视频| 亚洲视频一区二区免费在线观看| 欧美夜福利tv在线| 欧美黄色aaaa| 国产在线播放一区二区三区| 日韩午夜剧场| 久久夜色精品亚洲噜噜国产mv| 亚洲人体偷拍| 欧美在线免费观看亚洲| 欧美激情一区二区三区成人 | 国产一区二区三区精品欧美日韩一区二区三区 | 久久精品二区亚洲w码| 欧美激情亚洲自拍| 伊人蜜桃色噜噜激情综合| 亚洲一品av免费观看| 牛牛国产精品| 欧美亚洲一区| 国产精品久久久久久一区二区三区| 在线欧美日韩| 久久久久91| 亚洲香蕉在线观看| 欧美日韩国产黄| 尤物在线观看一区| 久久久久久有精品国产| 亚洲欧美综合精品久久成人 | 久久米奇亚洲| 国产亚洲综合性久久久影院| 亚洲一区二区精品| 亚洲精品国产日韩| 欧美久色视频| 99re热精品| 91久久精品日日躁夜夜躁欧美 | 欧美视频中文字幕在线| 亚洲电影下载| 美女国产精品| 久久成人免费| 黄色成人在线网站| 久久中文欧美| 久久精品免费播放| 玉米视频成人免费看| 久久综合色一综合色88| 久久精品动漫| 亚洲激情在线观看视频免费| 欧美成人国产va精品日本一级| 久久久久久精| 亚洲日本久久| 亚洲精品欧洲精品| 国产精品久久综合| 久久九九热免费视频| 久久深夜福利免费观看| 亚洲欧洲三级电影| 亚洲美女精品成人在线视频| 国产精品jizz在线观看美国| 亚洲欧美三级伦理| 欧美在线3区| 亚洲国产成人精品久久久国产成人一区 | 亚洲人成网站色ww在线| 欧美日本簧片| 欧美亚洲在线视频| 久久久久一区二区| 日韩图片一区| 亚洲欧美亚洲| 在线欧美影院| 99国产精品| 樱桃国产成人精品视频| 亚洲黄色性网站| 国产精品一区二区视频| 裸体歌舞表演一区二区 | 免费观看成人| 欧美韩日亚洲| 欧美一区二区三区在| 久久亚洲图片| 亚洲欧美国产精品va在线观看| 欧美在线地址| 亚洲图片你懂的| 久久伊人精品天天| 亚洲欧美国产一区二区三区| 久久国产精品第一页| 亚洲视频一区二区| 久久嫩草精品久久久精品| 亚洲小说欧美另类社区| 久久婷婷国产综合精品青草| 另类综合日韩欧美亚洲| aⅴ色国产欧美| 久久久久久久久岛国免费| 亚洲最新在线| 久久免费一区| 国产精品女人毛片| 亚洲视频www| 99在线热播精品免费| 日韩一级片网址| 国产一区二区三区日韩欧美| 91久久夜色精品国产九色| 国产日韩欧美在线播放不卡| 亚洲国产日韩欧美| 国内揄拍国内精品久久 | 在线中文字幕一区| 亚洲成色精品| 欧美一区二区三区在线观看视频| 国产精品99久久不卡二区| 美女诱惑黄网站一区| 久久亚洲春色中文字幕| 国产精品亚洲综合一区在线观看| 亚洲二区精品| 国产日韩欧美视频| 亚洲一区二区在线看| 中文精品99久久国产香蕉| 美女日韩在线中文字幕| 久热精品视频在线免费观看| 国产精品色一区二区三区| 亚洲三级观看| 99热在这里有精品免费| 久久国产精品72免费观看| 欧美一二三区精品| 欧美性猛片xxxx免费看久爱| 亚洲国产精品99久久久久久久久| 国产一区在线免费观看| 久久精品亚洲一区二区| 久久精品亚洲一区二区三区浴池| 国产精品www.| 亚洲伊人久久综合| 亚洲欧美不卡| 国产精品成人av性教育| 亚洲日本电影| 在线一区二区日韩| 欧美极品在线播放| 亚洲精品视频在线观看网站| 夜夜嗨av一区二区三区网页| 老牛国产精品一区的观看方式| 欧美激情一区二区三区| 亚洲精品国精品久久99热一 | 国产视频在线观看一区| 99视频一区二区| 亚洲综合欧美日韩| 欧美视频日韩视频在线观看| 日韩午夜在线| 亚洲专区欧美专区| 国产精品视频yy9099| 一区二区三区导航| 亚洲欧美视频一区| 在线观看亚洲精品视频| 久久久久一区二区| 亚洲日韩欧美视频| 久久国产婷婷国产香蕉| 狠狠色综合日日| 久久亚洲综合色| 亚洲欧洲一区二区在线观看| 亚洲精品美女免费| 国产精品久久久久影院色老大 | 久久久久久久久久久久久女国产乱 | 亚洲精品影视在线观看| 欧美福利专区| 99精品久久| 久久国产主播| 亚洲精品国产品国语在线app| 久久综合给合久久狠狠狠97色69| 亚洲精品日日夜夜| 久久久久国产成人精品亚洲午夜| 亚洲大胆视频| 国产精品你懂得| 久久久久久久成人| 99精品热视频只有精品10| 久久精品国产精品亚洲| 在线日韩av片| 国产精品毛片在线看| 午夜影视日本亚洲欧洲精品| 久久人人爽国产| 亚洲一区二区三区视频| 国产在线欧美日韩| 国产精品入口| 欧美国产一区二区三区激情无套| 亚洲午夜精品一区二区| 亚洲黄色一区| 毛片av中文字幕一区二区| 新片速递亚洲合集欧美合集| 亚洲精品乱码久久久久久日本蜜臀| 国产日韩欧美不卡| 欧美日韩另类视频| 久久综合亚洲社区| 久久国产精品99国产精| 亚洲私人影院| 欧美大片免费观看|