• <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>
            xiaoguozi's Blog
            Pay it forword - 我并不覺的自豪,我所嘗試的事情都失敗了······習(xí)慣原本生活的人不容易改變,就算現(xiàn)狀很糟,他們也很難改變,在過程中,他們還是放棄了······他們一放棄,大家就都是輸家······讓愛傳出去,很困難,也無法預(yù)料,人們需要更細(xì)心的觀察別人,要隨時注意才能保護(hù)別人,因為他們未必知道自己要什么·····

             內(nèi)存泄露相信對C++程序員來說都不陌生。解決內(nèi)存泄露的方案多種多樣,大部分方案以追蹤檢測為主,這種方法實現(xiàn)起來容易,使用方便,也比較安全。

                     首先我們要確定這個模塊的主要功能:

            1. 能追蹤內(nèi)存的分配和釋放過程。
            2. 要能顯示內(nèi)存分配的相關(guān)信息,比如內(nèi)存塊大小,代碼所在文件所在行等。
            3. 在發(fā)現(xiàn)內(nèi)存泄露時及時給出相關(guān)信息。
            4. 能正確處理一些異常情況,比如內(nèi)存不足,對象初始化失敗等等。
            5. 是線程安全的。[*這個還沒有實現(xiàn)]

                    有了一些基本功能需求,我們需要考慮每種功能怎么去實現(xiàn)。首先,我們可以通過重載的方式來追蹤new,delete.malloc和free,C++給我提供了這樣的特性。因為本文主要針對C++,所以主要講重載new,delete的方法,malloc和free的重載實現(xiàn)于此類似,最終版本的程序中也實現(xiàn)了malloc和free的重載。

            1.重載new和delete

                    首先我們要了解一下new和delete是怎么工作的。C++中的操作符最終都會被轉(zhuǎn)換成函數(shù)形式,例如"new int"會變成"opetaor new(sizeof(int))",而"new double[10]"會變成"operator new(sizeof(double)*10)",同樣“delete p”就變成了"operator delete(p)"。另外一個需要特別注意的地方是,new對于用戶定義的數(shù)據(jù)類型(即你的自定義類)會自動調(diào)用該類型的構(gòu)造函數(shù),如果構(gòu)造函數(shù)沒有拋出異常,則正確分配,否則會中斷分配操作,將異常傳遞給用戶。默認(rèn)情況下,new可以對象構(gòu)造異常進(jìn)行捕獲。另外一個版本的new就是不帶捕獲異常功能的的了,所以C++系統(tǒng)提供的new和delete有:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            void* operator new(size_t size)throw(std::bad_alloc);
            void* operator new[](size_t size) throw(std::bad_alloc);
            void* operator new(size_t,std::nothrow_t&)throw();
            void* operator new[](size_t,std::nothrow_t&)throw();
             
            void  operator delete(void* pointer);
            void  operator delete[](void* pointer);
            void  operator delete(void* pointer,std::nothrow_t&);
            void  operator delete[](void* pointer,std::nothrow_t&);<br>

                    其中,nothrow_t是一個空結(jié)構(gòu)體“struct nothrow_t{}",它的一個實例就是nothrow,C++用它來區(qū)分可以捕獲異常的new和不可捕獲異常的new。我們不能直接修改內(nèi)部函數(shù)的行為,但是我們可以重載它們。為了實現(xiàn)提供內(nèi)存分配信息的功能,我們給重載的函數(shù)加上幾個參數(shù)。得到以下函數(shù):

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            void* operator new(size_t size);
            void* operator new[](size_t size);
            void* operator new(size_t,std::nothrow_t&)throw();
            void* operator new[](size_t,std::nothrow_t&)throw();
            void* operator new(size_t size,const char* file,const char* func,const int line)throw(std::bad_alloc);
            void* operator new[](size_t size,const char* file,const char* func,const int line) throw(std::bad_alloc);
            void* operator delete(void* pointer);
            void* operator delete[](void* pointer);
            /*******Placement Delete********/
            void  operator delete(void* pointer,const char* file,const char* func,const int line);
            void  operator delete[](void* pointer,const char* file,const char* func,const int line);
            void  operator delete(void* pointer,std::nothrow_t&);
            void  operator delete[](void* pointer,std::nothrow_t&);
            /*******************************/

                     中間的幾個函數(shù),就是我們主要需要重載的函數(shù),模塊的大部分工作也都由著幾個函數(shù)完成。這些函數(shù)參數(shù)中,file表示分配代碼所在的文件名,func表示代碼所在的函數(shù)名,line表示代碼行號。這幾個參數(shù)信息我們可以通過編譯器預(yù)定義好的幾個宏來獲得:__FILE__,__FUNCTION__,__LINE__。也就是說可以將"new ..."替換成"new(__FILE__,__FUNCTION__,__LINE__) ...",最終成為"operator new(sizeof(...),__FILE__,__FUNCTION__,__LINE__)"的形式,也就達(dá)到了我們的目的。關(guān)于 placement delete將在下面詳細(xì)解釋。

            2.空間分配

                    接下來我們要考慮內(nèi)存分配信息的組織問題了。我們先來了解一下編譯器是怎么組織的。在大部分編譯器中,new所分配的空間都要大于實際申請的空間,大出來的部分就是編譯器定義的內(nèi)存塊的信息,包括了內(nèi)存塊的大小還有一些其他信息。如下圖所示:

                    我們把包含內(nèi)存分配信息的部分叫做cookie數(shù)據(jù)。為了方便,我們把cookie數(shù)據(jù)放在分配的內(nèi)存的起始位置,之后緊接有效數(shù)據(jù)區(qū)。我們還需要把返回給調(diào)用者的指針和new分配的數(shù)據(jù)區(qū)聯(lián)系起來,原本想用性能比較好的STL的map數(shù)據(jù)結(jié)構(gòu)來儲存這些數(shù)據(jù),但是map內(nèi)部同樣也使用new來分配內(nèi)存,所以如果直接使用map來儲存,就會陷入死循環(huán)中。所以這里我們必須自己現(xiàn)實一個數(shù)據(jù)結(jié)構(gòu)。我們可以對返回給調(diào)用者的地址進(jìn)行Hash,得到hash 表中的地址,具有相同Hash值的數(shù)據(jù)我們用一個單向鏈表連接起來。最終的數(shù)據(jù)結(jié)構(gòu)如下圖所示:

            2.1.構(gòu)造函數(shù)中的異常

                    另外一個必須要注意的一點(diǎn)是,new操作符會先分配空間然后調(diào)用用戶自定義類型的構(gòu)造函數(shù),如果構(gòu)造函數(shù)拋出異常,需要用戶手動釋放已分配的內(nèi)存。問題在于釋放這樣的內(nèi)存不能用一般的delete操作符,可以用一個例子來說明:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            #include <stdio.h>
            #include <stdexcept>
             
            void* operator new(size_t size, int line) {
                printf("Allocate %u bytes on line %d\\n", size, line);
                return operator new(size);
            }
             
            class UserClass {
            public:
                UserClass(int n)throw(){
                    if(n<=0){
                         throw std::runtime_error("n must be positive");
                    }
                }
            };
             
            int main(){
                try{
                    UserClass* myobj=new(__LINE__) UserClass(-10);
                    delete myobj; //doesn't work if placement was not defined
                } catch(const std::runtime_error& e) {
                    fprintf(stderr,"Exception: %s\n",e.what());
                }
                return 0;
            }<br>

                    這里,雖然在new過后試圖使用delete釋放已經(jīng)分配的內(nèi)存,但是實際上不會釋放。也許你的編譯器會給出這樣一條消息:

                    “no matching operator delete found”

                    為了正確處理這種情況,并給用戶提供相關(guān)的信息,我們需要定義placement delete操作符。placement delete是C++98標(biāo)準(zhǔn)中才有的一個特性,所以對于某些老的編譯器(大致可以認(rèn)為是指那些98年以前編寫的編譯器)不支持這個特性。這需要在模塊中添加宏定義讓用戶可以關(guān)閉placement delete的定義,以便模塊能在較老的編譯器上編譯。以下就是需要定義的placement delete操作符:

            1
            2
            3
            4
            void  operator delete(void* pointer,const char* file,const char* func,const int line);
            void  operator delete[](void* pointer,const char* file,const char* func,const int line);
            void  operator delete(void* pointer,std::nothrow_t&);
            void  operator delete[](void* pointer,std::nothrow_t&);<br>

            3.檢查內(nèi)存泄露

                    有了上面的實現(xiàn),我們可以方便的手動檢測內(nèi)存泄露。通過一個函數(shù)來實現(xiàn),它會檢索整個hash表,如果表不為空則存在內(nèi)存泄露。

                    為了達(dá)到在最后程序退出時檢查內(nèi)存泄露的目的,我們需要在所有對象調(diào)用析構(gòu)函數(shù)后進(jìn)行內(nèi)存泄露檢測,這是因為某些用戶類型在構(gòu)造函數(shù)里調(diào)用new而在析構(gòu)函數(shù)里調(diào)用delete。這樣做能大大的減小誤報的概率。而且因為對象的析構(gòu)函數(shù)的調(diào)用往往在主函數(shù)main()執(zhí)行結(jié)束之后進(jìn)行,所以我們也不能直接在主函數(shù)里進(jìn)行內(nèi)存泄露檢測。這里我們利用一個全局對象來實現(xiàn)這種檢測。首先我們定義一個類:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            class MemCheck{
                public:
                    MemCheck(){
                        memset(pTable,0,sizeof(mc_block_node_t*) * MC_HASHTABLESIZE);
                    }
                    ~MemCheck(){
                        if(mc_checkmem()){
                            abort();
                        }
                    }
            };

                    這里的構(gòu)造函數(shù)初始化Hash表。析構(gòu)函數(shù)檢測內(nèi)存泄露。然后定義一個MemCheck的全局靜態(tài)對象,這樣當(dāng)程序運(yùn)行之前會初始化hash表,程序退出時檢測內(nèi)存泄露。可是問題又來了,如果一個程序中有多個全局靜態(tài)對象會怎樣?不幸的是,對于全局靜態(tài)對象的構(gòu)造順序和析構(gòu)順序是C++標(biāo)準(zhǔn)中的一個未定義問題,也就是說,這個順序取決于編譯器的具體實現(xiàn)。考慮,絕大多數(shù)平臺使用VC和GCC編譯器,我們可以針對這兩種編譯器來控制全局對象的構(gòu)造和解析順序。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            #ifdef _MSC_VER
            #pragma init_seg(lib)
            #endif
             
            static MemCheck mc_autocheck_object
            #ifdef __GNUC__
            __attribute__((init_priority (101)))
            #endif
            ;

                    這里的宏定義部分都是編譯器的選項。

            posted on 2011-04-05 20:11 小果子 閱讀(1647) 評論(1)  編輯 收藏 引用 所屬分類: C++

            FeedBack:
            # re: c++ 內(nèi)存泄露(轉(zhuǎn))
            2011-08-04 00:25 | Nelson28CASSANDRA
            I opine that to get the <a href="http://bestfinance-blog.com">loans</a> from banks you must present a good reason. However, once I've got a commercial loan, because I wanted to buy a bike.   回復(fù)  更多評論
              
            久久精品免费一区二区| 久久电影网一区| 狠狠色综合网站久久久久久久| 国产精品久久久久久吹潮| 99久久99久久精品国产片果冻| 日日狠狠久久偷偷色综合0| 2021国内久久精品| 91精品久久久久久无码| 久久精品亚洲精品国产欧美| 97久久久久人妻精品专区| 久久精品无码一区二区WWW| 久久超碰97人人做人人爱| 国产69精品久久久久9999APGF| 99久久无码一区人妻| 97久久婷婷五月综合色d啪蜜芽| 日本免费一区二区久久人人澡| 无码久久精品国产亚洲Av影片| 天天做夜夜做久久做狠狠| 天天躁日日躁狠狠久久| 无码精品久久一区二区三区| 精品综合久久久久久97超人 | 国内精品人妻无码久久久影院| 岛国搬运www久久| 热久久国产精品| 久久综合九色综合网站| 精品熟女少妇AV免费久久| 精品人妻伦一二三区久久 | 久久青青国产| 久久综合九色综合精品| 人妻精品久久久久中文字幕69 | 久久久久亚洲AV成人网人人网站 | 99国产欧美久久久精品蜜芽| 一本色道久久88综合日韩精品| 亚洲国产精品嫩草影院久久| 色综合久久中文综合网| 久久精品国产半推半就| 久久99热国产这有精品| 色综合久久久久网| 久久久久无码中| 久久成人国产精品免费软件| 欧美亚洲国产精品久久|