• <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>

            海納百川

             

            2008年3月21日

            new/delete 操作符

            [TC++PL] new/delete 操作符
             
             
            Cpp Operators of new and delete
            1. 動(dòng)態(tài)內(nèi)存分配與釋放(new and delete)
            一般說來,一個(gè)對象的生命期是由它被創(chuàng)建時(shí)所處的區(qū)域決定的。例如,在一對{}類定義的一個(gè)對象,在離開這個(gè)由{}所界定的區(qū)域時(shí),該對象就會(huì)被銷毀,在這個(gè)區(qū)域之外這個(gè)對象是不存在的,程序的其他部分不能再引用這個(gè)對象了。

             

            如果希望在離開了創(chuàng)建這個(gè)對象時(shí)所處的區(qū)域后,還希望這個(gè)對象存在并能繼續(xù)引用它,就必須用new操作符在自由存儲(chǔ)空間來分配一個(gè)對象。這個(gè)過程也叫做動(dòng)態(tài)內(nèi)存分配,也叫堆對象。任何由new操作符分配的對象都應(yīng)該用delete操作符手動(dòng)地銷毀掉。C++標(biāo)準(zhǔn)并沒有定義任何形式的“垃圾收集”機(jī)制。delete操作符只能用于由new返回的指針,或者是零。當(dāng)delete的參數(shù)是0時(shí),不會(huì)產(chǎn)生任何效果,也就是說這個(gè)delete操作符對應(yīng)的函數(shù)根本就不會(huì)被執(zhí)行。

             

            new(delete)既可以分配(釋放)單個(gè)的對象(當(dāng)然也包括內(nèi)建類型),也可以分配(釋放)對象數(shù)組。下面是其函數(shù)原型:
            #include <new>

            void* operator new(size_t); // 參數(shù)是單個(gè)對象的大小

            void* operator new[](size_t); // 參數(shù)是對象數(shù)組的總的大小

            void delete(void*);

            void delete[](void*);

            C++標(biāo)準(zhǔn)中并沒有要求new操作符對分配出來的空間進(jìn)行初始化。下面是使用new分配一個(gè)字符數(shù)組的例子:
            char* save_string(const char* p)

            {
            char* s = new char[strlen(p)+1];

            // ...

            return s;

            }

             

            char* p = save_string(argv[1]);

            // ...

            delete[] p;

             


            class X { /* ... */ }

            X* p = new[10] X;

            X* p2 = new X[10];

            vector<int>* pv = new vector<int>(5);

            在new分配出足夠的空間后,編譯器會(huì)緊接著調(diào)用X的缺省構(gòu)造函數(shù)對空間進(jìn)行初始化。注意,上述兩種形式都是可以的,無論X是內(nèi)建類型還是自定義用戶類型。對于類來說,還可以使用類的構(gòu)造函數(shù)形式,如上面創(chuàng)建vector類型對象的例子。此外,一個(gè)用非數(shù)組形式的new操作符創(chuàng)建的對象,不能用數(shù)組形式的delete操作符來銷毀。

            2. 提供自己的內(nèi)存管理:重載new/delete操作符
            我們可以為new/delete定義自己的內(nèi)存管理方式,但是替換全局的new/delete操作符的實(shí)現(xiàn)是不夠好的,原因很明顯:有些人可能需要缺省的new/delete操作的一些方面,而另一些人則可能完全使用另外一種版本的實(shí)現(xiàn)。所以最好的辦法是為某個(gè)特定的類提供它自己的內(nèi)存管理方式。

             

            一個(gè)類的operator new()和operator delete()成員函數(shù),隱式地成為靜態(tài)成員函數(shù)。因此它們沒有this指針,也不能修改對象(很好理解,當(dāng)調(diào)用new的時(shí)候?qū)ο筮€沒有真正創(chuàng)建呢,當(dāng)然不能修改對象了!)。當(dāng)然在重載定義的時(shí)候,原型還是要與前面提到的一致。看下面這個(gè)例子:

            void* Employee::operator new(size_t s)

            {
            // 分配s字節(jié)的內(nèi)存空間,并返回這個(gè)空間的地址

             

            void Employee::operator delete(void* p, size_t s)

            {
            // 假定指針p是指向由Employee::operator new()分配的大小為s字節(jié)的內(nèi)存空間。

            // 釋放這塊空間以供系統(tǒng)在以后重用。

            }
            任何一個(gè)operator new()的操作符定義,都以一個(gè)尺寸值作為第一個(gè)參數(shù),且待分配對象的大小隱式給定,其值就作為new操作符函數(shù)的第一個(gè)參數(shù)值。

             


            在這里分配空間的具體實(shí)現(xiàn)可以是多種多樣的,可以直接使用malloc/free(缺省的全局new/delete大部分都是用的這種),可以在指定的內(nèi)存塊中分配空間(下節(jié)將要詳述),也可能還有其他的更好的更適合你的應(yīng)用的方式。

             

            那么如何重載數(shù)組形式的new[]/delete[]操作符呢?與普通形式一樣,只不過delete[]的參數(shù)形式稍有不同,如下所示:
            class Employee {

            public:
            void* operator new[](size_t);

            void operator delete[](void*); // 單參數(shù)形式,少了一個(gè)size_t參數(shù)

            void operator delete[](void*,size_t); //兩個(gè)參數(shù)形式也是可以的,但無必要

            // ...

            };

             

            在編譯器的內(nèi)部實(shí)現(xiàn)中,傳入new/delete[]的尺寸值可能是數(shù)組的大小s加上一個(gè)delta。這個(gè)delta量是編譯器的內(nèi)部實(shí)現(xiàn)所定義的某種額外開銷。為什么delete操作符不需要第二個(gè)尺寸參數(shù)呢?因?yàn)檫@個(gè)數(shù)組的大小s以及delta量都由系統(tǒng)“記住”了。但是delete[]的兩個(gè)參數(shù)形式的原型也是可以聲明的,在調(diào)用的時(shí)候會(huì)把s*sizeof(SomeClass)+delta作為第二個(gè)參數(shù)值傳入。delta量是與編譯器實(shí)現(xiàn)相關(guān)的,因此對于用戶程序員來說是不必要知道的。故而這里只提供單參數(shù)版本就可以了。(這倒是提供了一種查看這個(gè)delta量的方法。根據(jù)實(shí)際測試,GCC 4.1采用了4個(gè)字節(jié)的delta量。)

             


            到這里應(yīng)該注意到,當(dāng)我們調(diào)用operator delete()的時(shí)候,只給出了指針,并沒有給出對象大小的參數(shù)。那么編譯器是怎么知道應(yīng)該給operator delete()提供正確的尺寸值的呢?如果delete參數(shù)類型就是該對象的確切型別,那么這是一個(gè)簡單的事情,但是事情并不是總是這樣。看下面的例子:

            class Manager : public Employee {

            int level;

            // ...

            };
            void f()

            {
            Employee* p = new Manager; // 麻煩:確切型別丟失了!

            delete p;

            }
            這個(gè)時(shí)候編譯器不能得到正確的對象的尺寸。這就需要用戶的幫助了:只需要將基類的析構(gòu)函數(shù)聲明稱為虛函數(shù)即可。

             

            3. 在指定位置安放對象(Placement of Objects)
            new操作符的缺省方式是在自由內(nèi)存空間中創(chuàng)建對象。如果希望在指定的地方分配對象,就應(yīng)該使用這里介紹的方法。看下面的例子:
            class X {

            public:
            X(int);
            //...
            };
            當(dāng)需要把對象放置到指定地方的時(shí)候,只需要為分配函數(shù)提供一個(gè)額外的參數(shù)(既指定的某處內(nèi)存的地址),然后在使用new的時(shí)候提供這樣的一個(gè)額外參數(shù)即可。看下面的例子:
            void* operator new(size_t, void* p) { return p; } // 顯示安放操作符

            void* buf = reinterpret_cast<void*>(0xF00F); // 某個(gè)重要的地址

            X* p2 = new(buf) X; // 在buf地址處創(chuàng)建一個(gè)X對象,

            // 實(shí)際調(diào)用函數(shù)operator new(sizeof(X),buf)

             


            4. 內(nèi)存分配失敗與new_handler
            如果new操作符不能分配出內(nèi)存,會(huì)發(fā)生什么呢?默認(rèn)情況下,這個(gè)分配器會(huì)拋出一個(gè)bad_alloc異常對象。看下面的例子:
            void f()

            {
            try{
            for(;;) new char [10000];

            }
            catch(bad_alloc) {

            cerr << "Memory exhausted!\n";

            }
            }
            [疑問:構(gòu)造一個(gè)異常對象也需要內(nèi)存空間,既然已經(jīng)內(nèi)存耗盡了,那這個(gè)內(nèi)存又從哪里來呢?]
            可以自定義內(nèi)存耗盡時(shí)的處理方法(new_handler)。當(dāng)new操作失敗時(shí),首先會(huì)調(diào)用一個(gè)由set_new_handler()指定的函數(shù)。我們可以自定義這個(gè)函數(shù),然后用set_new_handler()來登記。最后當(dāng)new操作失敗時(shí)可以調(diào)用適當(dāng)?shù)淖远x處理過程。看下面的例子:
            #include <new> // set_new_handler()原型在此頭文件中

            void out_of_store()

            {
            cerr << "operator new failed: out of store\n";

            throw bad_alloc();

            }

             

            set_new_handler(out_of_store);
            for(;;) new char[10000];

            cout << "done\n";

             


            上述例子中控制流不會(huì)到達(dá)最后一句輸出,也就是說永遠(yuǎn)不會(huì)輸出done。而是會(huì)輸出:
            operator new failed: out of store

            自定義的new_handler函數(shù)的原型如下:
            typedef void (*new_handler)();


            5. 標(biāo)準(zhǔn)頭文件<new>中的原型
            下面是標(biāo)準(zhǔn)頭文件中的各種原型聲明:
            class bad_alloc : public exception { /* ... */ }

             


            struct nothrow_t { };

            extern struct nothrow_t nothrow; // 內(nèi)存分配器將不會(huì)拋出異常

             


            typedef void (*new_handler)();

            new_handler set_new_handler(new_handler new_p) throw();

             


            (1)普通的內(nèi)存分配,失敗時(shí)拋出bad_alloc異常
            // 單個(gè)對象的分配與釋放

            void* operator new(size_t) throw(bad_alloc);

            void operator delete(void*) throw();

            // 對象數(shù)組分配與釋放

            void* operator new[](size_t) throw(bad_alloc);

            void operator delete[](void*) throw();

             


            (2)與C方式兼容的內(nèi)存分配,失敗時(shí)返回0,不拋出異常
            // 單個(gè)對象分配與釋放

            void* operator new(size_t, const nothrow_t&) throw();

            void operator delete(void*, const nothrow_t&) throw();

            // 對象數(shù)組分配與釋放

            void* operator new[](size_t, const nothrow_t&) throw();

            void operator delete[](void*, const nothrow_t&) throw();

             


            (3)從指定空間中分配內(nèi)存
            // 分配已有空間給單個(gè)對象使用

            void* operator new(size_t, void* p) throw() { return p; }

            void operator delete(void* p, void*) throw() { } //什么都不做!

            // 分配已有空間給對象數(shù)組使用

            void* operator new[](size_t, void* p) throw() {return p;}

            void operator delete[](void* p, void*) throw() { } //什么也不做!

             


            在上述原型中,拋出空異常的函數(shù)都沒有辦法通過拋出std::bad_alloc發(fā)出內(nèi)存耗盡的信號(hào);它們在內(nèi)存分配失敗時(shí)返回0。

             

            上述原型的使用方法:
            class X {

            public:

            X(){};
            X(int n){};

            // ...

            };
            (1)可以拋出異常的new/delete操作符。
            原型的第一個(gè)參數(shù),即對象(或?qū)ο髷?shù)組)的大小,因此在使用時(shí)如下所示:
            X* p = new X;

            X* p1 = new X(5);

            X* pa = new X[10];

            分配對象數(shù)組時(shí)要注意:只能用這種形式,不能用帶參數(shù)的形式,例如下面的方式是錯(cuò)誤的:
            X* pa2 = new[20] X(5);

            你想分配一個(gè)X數(shù)組,每個(gè)數(shù)組元素都用5進(jìn)行初始化,這是不能做到的。

             

            (2)不拋出異常而返回0的new/delete操作符
            原型的第二個(gè)參數(shù)要求一個(gè)nothrow_t的引用,因此必須以<new>中定義的nothrow全局對象作為new/delete的參數(shù),如下所示:

            void f()

            {
            int* p = new int[10000]; // 可能會(huì)拋出bad_alloc異常

             


            if(int* q = new(nothrow) int[100000]; {

            // 內(nèi)存分配成功

            delete(nothrow)[]q;
            }
            else {

            // 內(nèi)存分配失敗

            }
            }

             

            6. new與異常
            如果在使用new構(gòu)造對象時(shí),構(gòu)造函數(shù)拋出了異常,結(jié)果會(huì)怎樣?由new分配的內(nèi)存釋放了嗎?在通常情況下答案是肯定的;但如果是在指定位置上分配對象空間,那么答案就不是這么簡單了。如果這個(gè)內(nèi)存塊是由某個(gè)類的new函數(shù)分配的,那么就會(huì)調(diào)用其相應(yīng)的delete函數(shù)(如果有的話),否則不會(huì)有釋放內(nèi)存的動(dòng)作發(fā)生。這種策略很好地處理了標(biāo)準(zhǔn)庫中的使用指定內(nèi)存的new操作符,以及提供了成對的分配與釋放函數(shù)的任何情形。
            看下面這個(gè)例子:
            void f(Arena& a, X* buffer)

            {
            X* p1 = new X;

            X* p2 = new X[10];

            X* p3 = new(buffer[10]) X;

            X* p4 = new(buffer[11]) X[10];

            X* p5 = new(a) X;

            X* p6 = new(a) X[10];

            }
            分析:p1和p2將能正確釋放其分配的內(nèi)存,不會(huì)造成內(nèi)存泄漏,這屬于一種正常情況。后面的四種情況則比較復(fù)雜。對象a如果是采用普通方式分配的內(nèi)存,那么將能夠正確釋放其擁有的內(nèi)存。

             

            7. malloc/free沒用了嗎?
            new能夠完全替代malloc嗎?絕大部分情況下,答案都是肯定的。但是有一種情況則非用malloc不可了。根據(jù)new的定義,其第一個(gè)參數(shù)是待分配對象的大小,但在使用時(shí)不需要明確地給出這個(gè)值。這個(gè)值是由編譯器暗中替你完成的。倘若在某種情況下,需要在分配一個(gè)對象的同時(shí)還要分配出一些額外的空間用來管理某些相關(guān)的信息。這個(gè)額外空間與對象的空間要求連續(xù)。這個(gè)時(shí)候new就幫不上了。必須用malloc把對象和額外空間的總大小作為malloc的參數(shù)。在分配出來了后,可能需要調(diào)用new的放置形式的調(diào)用在該塊內(nèi)存上構(gòu)造對象。
            8. 垃圾收集
            當(dāng)我們?yōu)樽约旱念愄峁┝俗约旱膬?nèi)存管理方法時(shí),有可能會(huì)出現(xiàn)內(nèi)存分配失敗的情況。因此我們可能會(huì)通過set_new_handler()提供一個(gè)更靈巧的內(nèi)存釋放與重用機(jī)制。這就為實(shí)現(xiàn)垃圾收集提供了一個(gè)實(shí)現(xiàn)思路。垃圾收集機(jī)制的基本思想是,當(dāng)一個(gè)對象不再被引用時(shí),它的內(nèi)存就可以安全地被新的對象所使用。
            當(dāng)比較垃圾收集機(jī)制與手工管理方式的代價(jià)時(shí),從一下幾個(gè)方面進(jìn)行比較:
            運(yùn)行時(shí)間,內(nèi)存的使用,可靠性,移植性,編程的費(fèi)用,垃圾收集器的費(fèi)用,性能的預(yù)期。

             

            垃圾收集器必須要處理幾個(gè)重要的問題:
            (1)指針的偽裝
            通常若以非指針的形式來存儲(chǔ)一個(gè)指針,則把這個(gè)指針叫做“偽裝的指針”。看下面這個(gè)例子:
            void f()

            {
            int* p = new int;

            long i1 = reinterpret_cast<long>(p) & 0xFFFF0000;

            long i2 = reinterpret_cast<long>(p) & 0x0000FFFF;

            p = 0;

            // 這里就不存在指向那個(gè)整型數(shù)的指針了!

            p = reinterpret_cast<int*>(i1|i2);

            // 現(xiàn)在這個(gè)整型數(shù)又被引用了!

            }
            上例中原本由p持有的指針被偽裝成兩個(gè)整型數(shù)i1和i2。垃圾收集器必須關(guān)注這種偽裝的指針。
            指針偽裝還有另外一種形式,即同時(shí)有指針和非指針成員的union結(jié)構(gòu),也會(huì)給垃圾收集器帶來特殊的問題。看下面的例子:
            union U {

            int* p;

            int i;

            };

             

            void f(U u, U u2, U u3)

            {
            u.p = new int;

            u2.i = 99999;

            u.i = 8;

            // ...

            }
            通常這是不可能知道union中包含的是指針還是整數(shù)。

             

            (2)delete函數(shù)
            通常若使用了自動(dòng)垃圾收集,那么delete和delete[]函數(shù)是不再需要了的。但是delete和delete[]函數(shù)除了釋放內(nèi)存的功能外,還會(huì)調(diào)用析構(gòu)函數(shù)。因此在這種情況下,下列調(diào)用
            delete p;

            就只調(diào)用析構(gòu)函數(shù),而內(nèi)存的復(fù)用則向后推遲,直到內(nèi)存塊被收集。一次回收多個(gè)對象,有助于減少碎片。

             

            (3)析構(gòu)函數(shù)
            當(dāng)垃圾收集器準(zhǔn)備回收對象時(shí),有兩種辦法可選:
            [1] 為這個(gè)對象調(diào)用析構(gòu)函數(shù)(如果有的話);

            [2] 將這個(gè)對象當(dāng)作原始內(nèi)存(即不調(diào)用析構(gòu)函數(shù))。

            一般垃圾收集器會(huì)選擇第二中方法。這種方法gc就成為模擬一種無限內(nèi)存的機(jī)制。
            也有可能設(shè)計(jì)一種gc,它能調(diào)用向它注冊了的對象的析構(gòu)函數(shù)。這種設(shè)計(jì)的一個(gè)重要方面是防止析構(gòu)函數(shù)重復(fù)刪除一個(gè)之前已經(jīng)刪除的對象。

             

            (4)內(nèi)存的碎片
            處理內(nèi)存碎片的問題上,有兩種主要的GC類型:拷貝型和保守型。拷貝型GC通過移動(dòng)對象使得碎片空間緊湊;而保守型則通過分配方式的改善來減少碎片。C++的觀點(diǎn)看來,更傾向于保守型的。因?yàn)橐苿?dòng)對象將導(dǎo)致大量的指針和引用等失效,所以拷貝型GC在C++中幾乎是不可能實(shí)現(xiàn)的。此外保守型GC也可以讓C++的代碼段與C代碼段共存。
             

            posted @ 2008-03-21 09:29 星羅棋布 閱讀(981) | 評(píng)論 (1)編輯 收藏

            僅列出標(biāo)題  

            導(dǎo)航

            統(tǒng)計(jì)

            常用鏈接

            留言簿(1)

            隨筆檔案

            友情鏈接

            搜索

            最新評(píng)論

            久久婷婷五月综合国产尤物app| 婷婷综合久久狠狠色99h| 亚洲婷婷国产精品电影人久久| 欧美久久久久久精选9999| 久久只有这精品99| 久久久久国产精品| 深夜久久AAAAA级毛片免费看 | 精品多毛少妇人妻AV免费久久| 久久久久综合中文字幕| 97精品依人久久久大香线蕉97| 欧美激情精品久久久久| 亚洲人成无码www久久久| 国产一久久香蕉国产线看观看| 欧美精品福利视频一区二区三区久久久精品 | 久久99亚洲综合精品首页| 久久人人爽人人爽人人av东京热| 精品久久久久久无码中文字幕一区| 久久久WWW成人| 久久久久夜夜夜精品国产| 亚洲国产精品久久电影欧美| 久久WWW免费人成—看片| 好久久免费视频高清| 久久亚洲精精品中文字幕| 亚洲精品NV久久久久久久久久| 久久婷婷综合中文字幕| 无码人妻久久一区二区三区免费丨 | 久久亚洲中文字幕精品一区四 | 久久精品国产精品亜洲毛片| 国产精品9999久久久久| 无码超乳爆乳中文字幕久久| 久久久久亚洲精品男人的天堂| 久久96国产精品久久久| 91精品国产综合久久精品| 久久综合久久自在自线精品自 | 久久线看观看精品香蕉国产| 色综合久久综合中文综合网| 综合久久一区二区三区| 久久99热这里只频精品6| 亚洲乱码日产精品a级毛片久久| 午夜视频久久久久一区 | 久久亚洲国产成人影院网站|