operator new在C++中的各種寫(xiě)法總結(jié)
乍一看,在C++中動(dòng)態(tài)分配內(nèi)存很簡(jiǎn)單:new是分配,delete是釋放,就這么簡(jiǎn)單。然而,這篇文章講得要復(fù)雜一點(diǎn),并且要考慮到自定義層次。這也許對(duì)簡(jiǎn)單的程序并不重要,但對(duì)你在代碼中控制內(nèi)存卻是十分必要的,是否能寫(xiě)一個(gè)自定義的分配器,某種高級(jí)內(nèi)存管理表或一個(gè)特定的垃圾回收機(jī)制。
這篇文章并不是一個(gè)綜合的手冊(cè),而是一個(gè)C++中各種內(nèi)存分配方法的概述。它面向已經(jīng)很熟悉C++語(yǔ)言的讀者。
原生operator new
我們先從原生operator new開(kāi)始??紤]如下代碼,它用來(lái)分配5個(gè)int型的空間并返回指向他們的指針[1]:
int* v = static_cast<int*>(::operator new(5 * sizeof(*v)));
當(dāng)像如上的調(diào)用,operator new扮演原生的內(nèi)存分配角色,類(lèi)似malloc。上面等價(jià)于:
int* v = static_cast<int*>(malloc(5 * sizeof(*v)));
釋放用operator new分配的內(nèi)存用operator delete:
::operator delete(v);
你愿意永遠(yuǎn)用原生new和delete函數(shù)嗎?是,只在極少數(shù)不用,我在下面的文章中會(huì)論證的。為什么用它們而不用原來(lái)的可信的malloc和free呢?一個(gè)很充分的原因就是你想保持代碼在C++領(lǐng)域的完整性?;旌鲜褂胣ew和free(或malloc和delete)是很不可取的(big NO NO)。用new和delete的另一個(gè)原因是你可以重載(overload)或重寫(xiě)(override)這些函數(shù),只要你需要。下面是個(gè)例子:
void* operator new(size_t sz) throw (std::bad_alloc)
{
cerr << "allocating " << sz << " bytesn";
void* mem = malloc(sz);
if (mem)
return mem;
else
throw std::bad_alloc();
}
void operator delete(void* ptr) throw()
{
cerr << "deallocating at " << ptr << endl;
free(ptr);
}
通常,注意到new被用來(lái)給內(nèi)置類(lèi)型,不包含用戶(hù)自定義new函數(shù)的類(lèi)的對(duì)象,和任意類(lèi)型的數(shù)組分配空間,使用的都是全局的運(yùn)算符new。當(dāng)new被用來(lái)為已經(jīng)被重定義new的類(lèi)實(shí)例化時(shí),用的就是那個(gè)類(lèi)的new函數(shù)。
下面來(lái)看下帶new函數(shù)的類(lèi)。
特定類(lèi)的operator new
大家有時(shí)很好奇"operator new"和"new operator"的區(qū)別。前者可以是一個(gè)重載的operator new,全局的或者特定類(lèi)或者原生的operator new。后者是你經(jīng)常用來(lái)分配內(nèi)存的C++內(nèi)置的new operator,就像:
Car* mycar = new Car;
C++支持操作符重載,并且我們可以重載的其中一個(gè)就是new。
下面是個(gè)例子:
class Base
{
public:
void* operator new(size_t sz)
{
cerr << "new " << sz << " bytesn";
return ::operator new(sz);
}
void operator delete(void* p)
{
cerr << "deleten";
::operator delete(p);
}
private:
int m_data;
};
class Derived : public Base
{
private:
int m_derived_data;
vector<int> z, y, x, w;
};
int main()
{
Base* b = new Base;
delete b;
Derived* d = new Derived;
delete d;
return 0;
}
打印結(jié)果:
new 4 bytes
delete
new 56 bytes
delete
在基類(lèi)被重載的operator new和operator delete也同樣被子類(lèi)繼承。如你所見(jiàn),operator new得到了兩個(gè)類(lèi)的正確大小。注意實(shí)際分配內(nèi)存時(shí)使用了::operator new,這是前面所描述過(guò)的原生new。在調(diào)用前面的兩個(gè)冒號(hào)很關(guān)鍵,是為了避免進(jìn)行無(wú)限遞歸(沒(méi)有它函數(shù)將一直調(diào)用自己下去)。
為什么你要為一個(gè)類(lèi)重載operator new?這里有許多理由。
性能:默認(rèn)的內(nèi)存分配算符被設(shè)計(jì)成通用的。有時(shí)你想分配給一個(gè)非常特殊的對(duì)象,通過(guò)自定義分配方式可以明顯地提高內(nèi)存管理。許多書(shū)和文章都討論了這種情況。尤其是"Modern C++ Design"的第4章展示了一個(gè)為較小的對(duì)象的非常好的設(shè)計(jì)并實(shí)現(xiàn)了自定義的分配算符。
調(diào)試 & 統(tǒng)計(jì):完全掌握內(nèi)存的分配和釋放為調(diào)試提供了很好的靈活性,統(tǒng)計(jì)信息和性能分析。你可將你的分配算符插入進(jìn)專(zhuān)門(mén)用來(lái)探測(cè)緩沖區(qū)溢出的守衛(wèi),通過(guò)分配算符和釋放算符(deallocations)的比較來(lái)檢測(cè)內(nèi)存泄漏,為統(tǒng)計(jì)和性能分析積累各種指標(biāo),等等。
個(gè)性化:對(duì)于非標(biāo)準(zhǔn)的內(nèi)存分配方式。一個(gè)很好的例子是內(nèi)存池或arenas,它們都使得內(nèi)存管理變得更簡(jiǎn)單。另一個(gè)例子是某個(gè)對(duì)象的完善的垃圾回收系統(tǒng),可以通過(guò)為一個(gè)類(lèi)或整個(gè)層面寫(xiě)你自己的operators new和delete。
研究在C++中new運(yùn)算符是很有幫助的。分配是分兩步進(jìn)行:
1. 首先,用全局operator new指導(dǎo)系統(tǒng)請(qǐng)求原生內(nèi)存。
2. 一旦請(qǐng)求內(nèi)存被分配,一個(gè)新的對(duì)象就在其中開(kāi)始構(gòu)造。
The C++ FAQ給出一個(gè)很好的例子,我很愿意在這里這出來(lái):
當(dāng)你寫(xiě)下這段代碼:
Foo* p = new Foo();
編譯器會(huì)生成類(lèi)似這種功能的代碼:
Foo* p;
// don't catch exceptions thrown by the allocator itself
//不用捕捉分配器自己拋出的異常
void* raw = operator new(sizeof(Foo));
// catch any exceptions thrown by the ctor
//捕捉ctor拋出的任何異常
try {
p = new(raw) Foo(); // call the ctor with raw as this 像這樣用raw調(diào)用ctor分配內(nèi)存
}
catch (...) {
// oops, ctor threw an exception 啊哦,ctor拋出了異常
operator delete(raw);
throw; // rethrow the ctor's exception 重新拋出ctor的異常
}
其中在try中很有趣的一段語(yǔ)法被稱(chēng)為"placement new",我們馬上就會(huì)討論到。為了使討論完整,我們來(lái)看下用delete來(lái)釋放一個(gè)對(duì)象時(shí)一個(gè)相似的情況,它也是分兩步進(jìn)行:
1.首先,將要被刪除對(duì)象的析構(gòu)函數(shù)被調(diào)用。
2.然后,被對(duì)象占用的內(nèi)存通過(guò)全局operator delete函數(shù)返還給系統(tǒng)。
所以:
delete p;
等價(jià)于[2]:
if (p != NULL) {
p->~Foo();
operator delete(p);
}
這時(shí)正適合我重復(fù)這篇文章第一段提到的,如果一個(gè)類(lèi)有它自己的operator new或 operator delete,這些函數(shù)將被調(diào)用,而不是調(diào)用全局的函數(shù)來(lái)分配和收回內(nèi)存。
Placement new
現(xiàn)在,回來(lái)我們上面看到樣例代碼中的"placement new"問(wèn)題。它恰好真的能用在C++代碼中的語(yǔ)法。首先,我想簡(jiǎn)單地解釋它如何工作。然后,我們將看到它在什么時(shí)候有用。
直接調(diào)用 placement new會(huì)跳過(guò)對(duì)象分配的第一步。也就是說(shuō)我們不會(huì)向操作系統(tǒng)請(qǐng)求內(nèi)存。而是告訴它有一塊內(nèi)存用來(lái)構(gòu)造對(duì)象[3]。下面的代碼表明了這點(diǎn):
int main(int argc, const char* argv[])
{
// A "normal" allocation. Asks the OS for memory, so we
// don't actually know where this ends up pointing.
//一個(gè)正常的分配。向操作系統(tǒng)請(qǐng)求內(nèi)存,所以我們并不知道它指向哪里
int* iptr = new int;
cerr << "Addr of iptr = " << iptr << endl;
// Create a buffer large enough to hold an integer, and
// note its address.
//創(chuàng)建一塊足夠大的緩沖區(qū)來(lái)保存一個(gè)整型,請(qǐng)注意它的地址
char mem[sizeof(int)];
cerr << "Addr of mem = " << (void*) mem << endl;
// Construct the new integer inside the buffer 'mem'.
// The address is going to be mem's.
//在緩沖區(qū)mem中構(gòu)造新的整型,地址將變成mem的地址
int* iptr2 = new (mem) int;
cerr << "Addr of iptr2 = " << iptr2 << endl;
return 0;
}
在我的機(jī)器上輸出如下:
Addr of iptr = 0x8679008
Addr of mem = 0xbfdd73d8
Addr of iptr2 = 0xbfdd73d8
如你所見(jiàn),placement new的結(jié)構(gòu)很簡(jiǎn)單。而有趣的問(wèn)題是,為什么我需要用這種東西?以下顯示了placement new在一些場(chǎng)景確實(shí)很有用:
· 自定義非侵入式內(nèi)存管理。當(dāng)為一個(gè)類(lèi)重載 operator new 同時(shí)也允許自定義內(nèi)存管理,這里關(guān)鍵概念是非侵入式。重載一個(gè)類(lèi)的 operator new需要你改變一個(gè)類(lèi)的源代碼。但假設(shè)我們有一個(gè)類(lèi)的代碼不想或者不能更改。我們?nèi)绾稳阅芸刂扑姆峙淠兀?Placement new就是答案。這種用 Placement new達(dá)到這個(gè)目的的通用編程技術(shù)叫做內(nèi)存池,有時(shí)候也叫arenas[4]。
· 在一些程序中,在指定內(nèi)存區(qū)域的分配對(duì)象是很必要的。一個(gè)例子是共享內(nèi)存。另一個(gè)例子是嵌入式程序或使用內(nèi)存映射的周邊驅(qū)動(dòng)程序,這些都可以很方便地在它們的“領(lǐng)地”分配對(duì)象。
· 許多容器庫(kù)預(yù)先分配很大一塊內(nèi)存空間。當(dāng)一個(gè)對(duì)象被添加,它們就必須在這里構(gòu)造,因此就用上了placement new。典型的例子就是標(biāo)準(zhǔn)vector容器。
刪除用placement new 分配的對(duì)象
一條C++箴言就是一個(gè)用new創(chuàng)建的對(duì)象應(yīng)該用delete來(lái)釋放。這個(gè)對(duì)placement new 同樣適用嗎?不完全是:
int main(int argc, const char* argv[])
{
char mem[sizeof(int)];
int* iptr2 = new (mem) int;
delete iptr2; // Whoops, segmentation fault! 嗚啊,段錯(cuò)誤啦!
return 0;
}
為了理解上面代碼片段為什么delete iptr2會(huì)引起段錯(cuò)誤(或某種內(nèi)存異常,這個(gè)因操作系統(tǒng)而異),讓我們回想下delete iptr2實(shí)際干了什么:
1. First, the destructor of the object that's being deleted is called.
首先,調(diào)用將要被刪除的對(duì)象的析構(gòu)函數(shù)。
2. Then, the memory occupied by the object is returned to the OS, represented by the global operator delete function.
然后,這個(gè)對(duì)象在操作系統(tǒng)中占用的內(nèi)存用全局operator delete函數(shù)收回。
對(duì)于用placement new分配的對(duì)象,第一步是沒(méi)有問(wèn)題的,但第二步就可疑了。嘗試釋放一段沒(méi)有被分配算符實(shí)際分配的內(nèi)存就不對(duì)了,但上面的代碼確實(shí)這么做了。iptr2指向了一段并沒(méi)有用全局operator new分配的棧中的一段位置。然而,delete iptr2將嘗試用全局operator delete來(lái)釋放內(nèi)存。當(dāng)然會(huì)段錯(cuò)誤啦。
那么我們應(yīng)該怎么辦?我們應(yīng)該怎樣正確地刪除iptr2?當(dāng)然,我們肯定不會(huì)認(rèn)為編譯器怎么會(huì)解決怎么翻譯內(nèi)存,畢竟,我們只是傳了一個(gè)指針給placement new,那個(gè)指針可能是從棧里拿,從內(nèi)存池里或者別的地方。所以必須手動(dòng)根據(jù)實(shí)際情況來(lái)釋放。
事實(shí)上,上面的placement new用法只是C++的new指定額外參數(shù)的廣義placement new語(yǔ)法的一種特例。它在標(biāo)準(zhǔn)頭文件中定義如下:
inline void* operator new(std::size_t, void* __p) throw()
{
return __p;
}
C++一個(gè)對(duì)應(yīng)的帶有相同參數(shù)的delete也被找到,它用來(lái)釋放一個(gè)對(duì)象。它在頭文件中定義如下:
inline void operator delete (void*, void*) throw()
{
}
的確,C++運(yùn)行并不知道怎么釋放一個(gè)對(duì)象,所以delete函數(shù)沒(méi)有操作。
怎么析構(gòu)呢?對(duì)于一個(gè)int,并不真的需要一個(gè)析構(gòu)函數(shù),但假設(shè)代碼是這樣的:
char mem[sizeof(Foo)];
Foo* fooptr = new (mem) Foo;
對(duì)于某個(gè)有意義的類(lèi)Foo。我們一旦不需要fooptr了,應(yīng)該怎么析構(gòu)它呢?我們必須顯式調(diào)用它的析構(gòu)函數(shù):
fooptr->~Foo();
對(duì),顯式調(diào)用析構(gòu)函數(shù)在C++中是合法的,并且這也是唯一一種正確的做法[5]。
結(jié)論
這是一個(gè)復(fù)雜的主題,并且這篇文章只起到一個(gè)介紹的作用,對(duì)C++的多種內(nèi)存分配方法給出了一種“嘗鮮”。一旦你研究一些細(xì)節(jié)會(huì)發(fā)現(xiàn)還有許多有趣的編程技巧(例如,實(shí)現(xiàn)一個(gè)內(nèi)存池分配)。這些問(wèn)題最好是在有上下文的情況下提出,而不是作為一個(gè)普通的介紹性文章的一部分。如果你想知道得更多,請(qǐng)查閱下面的資源列表。
資源
· C++ FAQ Lite, especially items 11.14 and 16.9
· "The C++ Programming Language, 3rd edition" by Bjarne Stroustrup – 10.4.11
· "Effective C++, 3rd edition" by Scott Myers – item 52
· "Modern C++ Design" by Andrei Alexandrescu – chapter 4
· Several StackOverflow discussions. Start with this one and browse as long as your patience lasts.
我仍會(huì)在operator | |
[2] |
注意到這里是檢查是否為NULL。這樣做使delete |
[3] |
對(duì)傳給placement |
[4] |
內(nèi)存池本身是一個(gè)很大且迷人的話題。我并不打算在這里擴(kuò)展,所以我鼓勵(lì)你自己上網(wǎng)找些信息,WIKI如往常一樣是個(gè)好地方(good |
[5] |
事實(shí)上,標(biāo)準(zhǔn)的vector容器用這種方法去析構(gòu)它保存的數(shù)據(jù)。 |
上一篇:C++中overload,override,overwrite的區(qū)別詳細(xì)解析
欄 目:C語(yǔ)言
本文標(biāo)題:operator new在C++中的各種寫(xiě)法總結(jié)
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/4166.html
您可能感興趣的文章
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎么打出三角函數(shù)的值
- 01-10APUE筆記之:進(jìn)程環(huán)境詳解
- 01-10c++中inline的用法分析
- 01-10深入理解堆排序及其分析
- 01-10深入C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式詳解
- 01-10深入解析Linux下\r\n的問(wèn)題
- 01-10基于getline()函數(shù)的深入理解
- 01-10深入理解C/C++混合編程
- 01-10深入探討C語(yǔ)言中局部變量與全局變量在內(nèi)存中的存放位置


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 04-02jquery與jsp,用jquery
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?