0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

了解一下比較復(fù)雜也非常神秘的new

C語(yǔ)言專家集中營(yíng) ? 來(lái)源:未知 ? 作者:胡薇 ? 2018-04-23 15:27 ? 次閱讀

“new”是C++的一個(gè)關(guān)鍵字,同時(shí)也是操作符。關(guān)于new的話題非常多,因?yàn)樗_實(shí)比較復(fù)雜,也非常神秘,下面我將把我了解到的與new有關(guān)的內(nèi)容做一個(gè)總結(jié)。

new的過(guò)程

當(dāng)我們使用關(guān)鍵字new在堆上動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象時(shí),它實(shí)際上做了三件事:獲得一塊內(nèi)存空間、調(diào)用構(gòu)造函數(shù)、返回正確的指針。當(dāng)然,如果我們創(chuàng)建的是簡(jiǎn)單類型的變量,那么第二步會(huì)被省略。假如我們定義了如下一個(gè)類A:

class A{ int i;public: A(int _i) :i(_i*_i) {} void Say() { printf("i=%d/n", i); }};//調(diào)用new:A* pa = new A(3);

那么上述動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象的過(guò)程大致相當(dāng)于以下三句話(只是大致上):

A* pa = (A*)malloc(sizeof(A));pa->A::A(3);return pa;

雖然從效果上看,這三句話也得到了一個(gè)有效的指向堆上的A對(duì)象的指針pa,但區(qū)別在于,當(dāng)malloc失敗時(shí),它不會(huì)調(diào)用分配內(nèi)存失敗處理程序new_handler,而使用new的話會(huì)的。因此我們還是要盡可能的使用new,除非有一些特殊的需求。

new的三種形態(tài)

到目前為止,本文所提到的new都是指的“new operator”或稱為“new expression”,但事實(shí)上在C++中一提到new,至少可能代表以下三種含義:new operator、operator new、placement new。

new operator就是我們平時(shí)所使用的new,其行為就是前面所說(shuō)的三個(gè)步驟,我們不能更改它。但具體到某一步驟中的行為,如果它不滿足我們的具體要求時(shí),我們是有可能更改它的。三個(gè)步驟中最后一步只是簡(jiǎn)單的做一個(gè)指針的類型轉(zhuǎn)換,沒(méi)什么可說(shuō)的,并且在編譯出的代碼中也并不需要這種轉(zhuǎn)換,只是人為的認(rèn)識(shí)罷了。但前兩步就有些內(nèi)容了。

new operator的第一步分配內(nèi)存實(shí)際上是通過(guò)調(diào)用operator new來(lái)完成的,這里的new實(shí)際上是像加減乘除一樣的操作符,因此也是可以重載的。operator new默認(rèn)情況下首先調(diào)用分配內(nèi)存的代碼,嘗試得到一段堆上的空間,如果成功就返回,如果失敗,則轉(zhuǎn)而去調(diào)用一個(gè)new_hander,然后繼續(xù)重復(fù)前面過(guò)程。如果我們對(duì)這個(gè)過(guò)程不滿意,就可以重載operator new,來(lái)設(shè)置我們希望的行為。例如:

class A{public: void* operator new(size_t size) { printf("operator new called/n"); return ::operator new(size); }};A* a = new A();

這里通過(guò)::operator new調(diào)用了原有的全局的new,實(shí)現(xiàn)了在分配內(nèi)存之前輸出一句話。全局的operator new也是可以重載的,但這樣一來(lái)就不能再遞歸的使用new來(lái)分配內(nèi)存,而只能使用malloc了:

void* operator new(size_t size){ printf("global new/n");return malloc(size);}

相應(yīng)的,delete也有delete operator和operator delete之分,后者也是可以重載的。并且,如果重載了operator new,就應(yīng)該也相應(yīng)的重載operator delete,這是良好的編程習(xí)慣。

new的第三種形態(tài)——placement new是用來(lái)實(shí)現(xiàn)定位構(gòu)造的,因此可以實(shí)現(xiàn)new operator三步操作中的第二步,也就是在取得了一塊可以容納指定類型對(duì)象的內(nèi)存后,在這塊內(nèi)存上構(gòu)造一個(gè)對(duì)象,這有點(diǎn)類似于前面代碼中的“p->A::A(3);”這句話,但這并不是一個(gè)標(biāo)準(zhǔn)的寫(xiě)法,正確的寫(xiě)法是使用placement new:

#include void main(){ char s[sizeof(A)]; A* p = (A*)s; new(p) A(3); //p->A::A(3); p->Say();}

對(duì)頭文件的引用是必須的,這樣才可以使用placement new。這里“new(p) A(3)”這種奇怪的寫(xiě)法便是placement new了,它實(shí)現(xiàn)了在指定內(nèi)存地址上用指定類型的構(gòu)造函數(shù)來(lái)構(gòu)造一個(gè)對(duì)象的功能,后面A(3)就是對(duì)構(gòu)造函數(shù)的顯式調(diào)用。這里不難發(fā)現(xiàn),這塊指定的地址既可以是棧,又可以是堆,placement對(duì)此不加區(qū)分。但是,除非特別必要,不要直接使用placement new ,這畢竟不是用來(lái)構(gòu)造對(duì)象的正式寫(xiě)法,只不過(guò)是new operator的一個(gè)步驟而已。使用new operator地編譯器會(huì)自動(dòng)生成對(duì)placement new的調(diào)用的代碼,因此也會(huì)相應(yīng)的生成使用delete時(shí)調(diào)用析構(gòu)函數(shù)的代碼。如果是像上面那樣在棧上使用了placement new,則必須手工調(diào)用析構(gòu)函數(shù),這也是顯式調(diào)用析構(gòu)函數(shù)的唯一情況:

p->~A();

當(dāng)我們覺(jué)得默認(rèn)的new operator對(duì)內(nèi)存的管理不能滿足我們的需要,而希望自己手工的管理內(nèi)存時(shí),placement new就有用了。STL中的allocator就使用了這種方式,借助placement new來(lái)實(shí)現(xiàn)更靈活有效的內(nèi)存管理。

處理內(nèi)存分配異常

正如前面所說(shuō),operator new的默認(rèn)行為是請(qǐng)求分配內(nèi)存,如果成功則返回此內(nèi)存地址,如果失敗則調(diào)用一個(gè)new_handler,然后再重復(fù)此過(guò)程。于是,想要從operator new的執(zhí)行過(guò)程中返回,則必然需要滿足下列條件之一:

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

lnew_handler中拋出bad_alloc異常

lnew_handler中調(diào)用exit()或類似的函數(shù),使程序結(jié)束

于是,我們可以假設(shè)默認(rèn)情況下operator new的行為是這樣的:

void* operator new(size_t size){ void* p = null while(!(p = malloc(size))) { if(null == new_handler) throw bad_alloc(); try { new_handler(); } catch(bad_alloc e) { throw e; } catch(…) {} } return p;}

在默認(rèn)情況下,new_handler的行為是拋出一個(gè)bad_alloc異常,因此上述循環(huán)只會(huì)執(zhí)行一次。但如果我們不希望使用默認(rèn)行為,可以自定義一個(gè)new_handler,并使用std::set_new_handler函數(shù)使其生效。在自定義的new_handler中,我們可以拋出異常,可以結(jié)束程序,也可以運(yùn)行一些代碼使得有可能有內(nèi)存被空閑出來(lái),從而下一次分配時(shí)也許會(huì)成功,也可以通過(guò)set_new_handler來(lái)安裝另一個(gè)可能更有效的new_handler。例如:

void MyNewHandler(){ printf(“New handler called!/n”); throw std::bad_alloc();}std::set_new_handler(MyNewHandler);

這里new_handler程序在拋出異常之前會(huì)輸出一句話。應(yīng)該注意,在new_handler的代碼里應(yīng)該注意避免再嵌套有對(duì)new的調(diào)用,因?yàn)槿绻@里調(diào)用new再失敗的話,可能會(huì)再導(dǎo)致對(duì)new_handler的調(diào)用,從而導(dǎo)致無(wú)限遞歸調(diào)用。——這是我猜的,并沒(méi)有嘗試過(guò)。

在編程時(shí)我們應(yīng)該注意到對(duì)new的調(diào)用是有可能有異常被拋出的,因此在new的代碼周圍應(yīng)該注意保持其事務(wù)性,即不能因?yàn)檎{(diào)用new失敗拋出異常來(lái)導(dǎo)致不正確的程序邏輯或數(shù)據(jù)結(jié)構(gòu)的出現(xiàn)。例如:

class SomeClass{ static int count; SomeClass() {}public: static SomeClass* GetNewInstance() { count++; return new SomeClass(); }};

靜態(tài)變量count用于記錄此類型生成的實(shí)例的個(gè)數(shù),在上述代碼中,如果因new分配內(nèi)存失敗而拋出異常,那么其實(shí)例個(gè)數(shù)并沒(méi)有增加,但count變量的值卻已經(jīng)多了一個(gè),從而數(shù)據(jù)結(jié)構(gòu)被破壞。正確的寫(xiě)法是:

static SomeClass* GetNewInstance(){ SomeClass* p = new SomeClass(); count++; return p;}

這樣一來(lái),如果new失敗則直接拋出異常,count的值不會(huì)增加。類似的,在處理線程同步時(shí),也要注意類似的問(wèn)題:

void SomeFunc(){ lock(someMutex); //加一個(gè)鎖 delete p; p = new SomeClass(); unlock(someMutex);}

此時(shí),如果new失敗,unlock將不會(huì)被執(zhí)行,于是不僅造成了一個(gè)指向不正確地址的指針p的存在,還將導(dǎo)致someMutex永遠(yuǎn)不會(huì)被解鎖。這種情況是要注意避免的。(參考:C++箴言:爭(zhēng)取異常安全的代碼)

STL的內(nèi)存分配與traits技巧

在《STL原碼剖析》一書(shū)中詳細(xì)分析了SGI STL的內(nèi)存分配器的行為。與直接使用new operator不同的是,SGI STL并不依賴C++默認(rèn)的內(nèi)存分配方式,而是使用一套自行實(shí)現(xiàn)的方案。首先SGI STL將可用內(nèi)存整塊的分配,使之成為當(dāng)前進(jìn)程可用的內(nèi)存,當(dāng)程序中確實(shí)需要分配內(nèi)存時(shí),先從這些已請(qǐng)求好的大內(nèi)存塊中嘗試取得內(nèi)存,如果失敗的話再嘗試整塊的分配大內(nèi)存。這種做法有效的避免了大量?jī)?nèi)存碎片的出現(xiàn),提高了內(nèi)存管理效率。

為了實(shí)現(xiàn)這種方式,STL使用了placement new,通過(guò)在自己管理的內(nèi)存空間上使用placement new來(lái)構(gòu)造對(duì)象,以達(dá)到原有new operator所具有的功能。

template inline void construct(T1* p, const T2& value){ new(p) T1(value);}

此函數(shù)接收一個(gè)已構(gòu)造的對(duì)象,通過(guò)拷貝構(gòu)造的方式在給定的內(nèi)存地址p上構(gòu)造一個(gè)新對(duì)象,代碼中后半截T1(value)便是placement new語(yǔ)法中調(diào)用構(gòu)造函數(shù)的寫(xiě)法,如果傳入的對(duì)象value正是所要求的類型T1,那么這里就相當(dāng)于調(diào)用拷貝構(gòu)造函數(shù)。類似的,因使用了placement new,編譯器不會(huì)自動(dòng)產(chǎn)生調(diào)用析構(gòu)函數(shù)的代碼,需要手工的實(shí)現(xiàn):

template inline void destory(T* pointer){ pointer->~T();}

與此同時(shí),STL中還有一個(gè)接收兩個(gè)迭代器的destory版本,可將某容器上指定范圍內(nèi)的對(duì)象全部銷毀。典型的實(shí)現(xiàn)方式就是通過(guò)一個(gè)循環(huán)來(lái)對(duì)此范圍內(nèi)的對(duì)象逐一調(diào)用析構(gòu)函數(shù)。如果所傳入的對(duì)象是非簡(jiǎn)單類型,這樣做是必要的,但如果傳入的是簡(jiǎn)單類型,或者根本沒(méi)有必要調(diào)用析構(gòu)函數(shù)的自定義類型(例如只包含數(shù)個(gè)int成員的結(jié)構(gòu)體),那么再逐一調(diào)用析構(gòu)函數(shù)是沒(méi)有必要的,也浪費(fèi)了時(shí)間。為此,STL使用了一種稱為“type traits”的技巧,在編譯器就判斷出所傳入的類型是否需要調(diào)用析構(gòu)函數(shù):

template inline void destory(ForwardIterator first, ForwardIterator last){ __destory(first, last, value_type(first));}

其中value_type()用于取出迭代器所指向的對(duì)象的類型信息,于是:

templateinline void __destory(ForwardIterator first, ForwardIterator last, T*){ typedef typename __type_traits::has_trivial_destructor trivial_destructor; __destory_aux(first, last, trivial_destructor());}//如果需要調(diào)用析構(gòu)函數(shù):templateinline void __destory_aux(ForwardIterator first, ForwardIterator last, __false_type){ for(; first < last; ++first)?????? destory(&*first); //因first是迭代器,*first取出其真正內(nèi)容,然后再用&取地址}//如果不需要,就什么也不做:tempalteinline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type){}

因上述函數(shù)全都是inline的,所以多層的函數(shù)調(diào)用并不會(huì)對(duì)性能造成影響,最終編譯的結(jié)果根據(jù)具體的類型就只是一個(gè)for循環(huán)或者什么都沒(méi)有。這里的關(guān)鍵在于__type_traits這個(gè)模板類上,它根據(jù)不同的T類型定義出不同的has_trivial_destructor的結(jié)果,如果T是簡(jiǎn)單類型,就定義為_(kāi)_true_type類型,否則就定義為_(kāi)_false_type類型。其中__true_type、__false_type只不過(guò)是兩個(gè)沒(méi)有任何內(nèi)容的類,對(duì)程序的執(zhí)行結(jié)果沒(méi)有什么意義,但在編譯器看來(lái)它對(duì)模板如何特化就具有非常重要的指導(dǎo)意義了,正如上面代碼所示的那樣。__type_traits也是特化了的一系列模板類:

struct __true_type {};struct __false_type {};template struct __type_traits{public: typedef __false _type has_trivial_destructor;……};template<>//模板特化struct __type_traits //int的特化版本{public: typedef __true_type has_trivial_destructor;……};…… //其他簡(jiǎn)單類型的特化版本

如果要把一個(gè)自定義的類型MyClass也定義為不調(diào)用析構(gòu)函數(shù),只需要相應(yīng)的定義__type_traits的一個(gè)特化版本即可:

template<>struct __type_traits{public: typedef __true_type has_trivial_destructor;……};

模板是比較高級(jí)的C++編程技巧,模板特化、模板偏特化就更是技巧性很強(qiáng)的東西,STL中的type_traits充分借助模板特化的功能,實(shí)現(xiàn)了在程序編譯期通過(guò)編譯器來(lái)決定為每一處調(diào)用使用哪個(gè)特化版本,于是在不增加編程復(fù)雜性的前提下大大提高了程序的運(yùn)行效率。更詳細(xì)的內(nèi)容可參考《STL源碼剖析》第二、三章中的相關(guān)內(nèi)容。

帶有“[]”的new和delete

我們經(jīng)常會(huì)通過(guò)new來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)數(shù)組,例如:

char* s = new char[100];……delete s;

嚴(yán)格的說(shuō),上述代碼是不正確的,因?yàn)槲覀冊(cè)诜峙鋬?nèi)存時(shí)使用的是new[],而并不是簡(jiǎn)單的new,但釋放內(nèi)存時(shí)卻用的是delete。正確的寫(xiě)法是使用delete[]:

delete[] s;

但是,上述錯(cuò)誤的代碼似乎也能編譯執(zhí)行,并不會(huì)帶來(lái)什么錯(cuò)誤。事實(shí)上,new與new[]、delete與delete[]是有區(qū)別的,特別是當(dāng)用來(lái)操作復(fù)雜類型時(shí)。假如針對(duì)一個(gè)我們自定義的類MyClass使用new[]:

MyClass* p = new MyClass[10];

上述代碼的結(jié)果是在堆上分配了10個(gè)連續(xù)的MyClass實(shí)例,并且已經(jīng)對(duì)它們依次調(diào)用了構(gòu)造函數(shù),于是我們得到了10個(gè)可用的對(duì)象,這一點(diǎn)與JavaC#有區(qū)別的,Java、C#中這樣的結(jié)果只是得到了10個(gè)null。換句話說(shuō),使用這種寫(xiě)法時(shí)MyClass必須擁有不帶參數(shù)的構(gòu)造函數(shù),否則會(huì)發(fā)現(xiàn)編譯期錯(cuò)誤,因?yàn)榫幾g器無(wú)法調(diào)用有參數(shù)的構(gòu)造函數(shù)。

當(dāng)這樣構(gòu)造成功后,我們可以再將其釋放,釋放時(shí)使用delete[]:

delete[] p;

當(dāng)我們對(duì)動(dòng)態(tài)分配的數(shù)組調(diào)用delete[]時(shí),其行為根據(jù)所申請(qǐng)的變量類型會(huì)有所不同。如果p指向簡(jiǎn)單類型,如int、char等,其結(jié)果只不過(guò)是這塊內(nèi)存被回收,此時(shí)使用delete[]與delete沒(méi)有區(qū)別,但如果p指向的是復(fù)雜類型,delete[]會(huì)針對(duì)動(dòng)態(tài)分配得到的每個(gè)對(duì)象調(diào)用析構(gòu)函數(shù),然后再釋放內(nèi)存。因此,如果我們對(duì)上述分配得到的p指針直接使用delete來(lái)回收,雖然編譯期不報(bào)什么錯(cuò)誤(因?yàn)榫幾g器根本看不出來(lái)這個(gè)指針p是如何分配的),但在運(yùn)行時(shí)(DEBUG情況下)會(huì)給出一個(gè)Debug assertion failed提示。

到這里,我們很容易提出一個(gè)問(wèn)題——delete[]是如何知道要為多少個(gè)對(duì)象調(diào)用析構(gòu)函數(shù)的?要回答這個(gè)問(wèn)題,我們可以首先看一看new[]的重載。

class MyClass{int a;public: MyClass() { printf("ctor/n"); } ~MyClass() { printf("dtor/n"); }};void* operator new[](size_t size){void* p = operator new(size); printf("calling new[] with size=%d address=%p/n", size, p);return p;}// 主函數(shù)MyClass* mc = new MyClass[3];printf("address of mc=%p/n", mc);delete[] mc;

運(yùn)行此段代碼,得到的結(jié)果為:(VC2005)

calling new[] with size=16address=003A5A58

ctor

ctor

ctor

address of mc=003A5A5C

dtor

dtor

dtor

雖然對(duì)構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用結(jié)果都在預(yù)料之中,但所申請(qǐng)的內(nèi)存空間大小以及地址的數(shù)值卻出現(xiàn)了問(wèn)題。我們的類MyClass的大小顯然是4個(gè)字節(jié),并且申請(qǐng)的數(shù)組中有3個(gè)元素,那么應(yīng)該一共申請(qǐng)12個(gè)字節(jié)才對(duì),但事實(shí)上系統(tǒng)卻為我們申請(qǐng)了16字節(jié),并且在operator new[]返后我們得到的內(nèi)存地址是實(shí)際申請(qǐng)得到的內(nèi)存地址值加4的結(jié)果。也就是說(shuō),當(dāng)為復(fù)雜類型動(dòng)態(tài)分配數(shù)組時(shí),系統(tǒng)自動(dòng)在最終得到的內(nèi)存地址前空出了4個(gè)字節(jié),我們有理由相信這4個(gè)字節(jié)的內(nèi)容與動(dòng)態(tài)分配數(shù)組的長(zhǎng)度有關(guān)。通過(guò)單步跟蹤,很容易發(fā)現(xiàn)這4個(gè)字節(jié)對(duì)應(yīng)的int值為0x00000003,也就是說(shuō)記錄的是我們分配的對(duì)象的個(gè)數(shù)。改變一下分配的個(gè)數(shù)然后再次觀察的結(jié)果證實(shí)了我的想法。于是,我們也有理由認(rèn)為new[] operator的行為相當(dāng)于下面的偽代碼:

template T* New[](int count){ int size = sizeof(T) * count + 4; void* p = T::operator new[](size); *(int*)p = count; T* pt = (T*)((int)p + 4); for(int i = 0; i < count; i++)?????? new(&pt[i]) T();?? return pt;}

上述示意性的代碼省略了異常處理的部分,只是展示當(dāng)我們對(duì)一個(gè)復(fù)雜類型使用new[]來(lái)動(dòng)態(tài)分配數(shù)組時(shí)其真正的行為是什么,從中可以看到它分配了比預(yù)期多4個(gè)字節(jié)的內(nèi)存并用它來(lái)保存對(duì)象的個(gè)數(shù),然后對(duì)于后面每一塊空間使用placement new來(lái)調(diào)用無(wú)參構(gòu)造函數(shù),這也就解釋了為什么這種情況下類必須有無(wú)參構(gòu)造函數(shù),最后再將首地址返回。類似的,我們很容易寫(xiě)出相應(yīng)的delete[]的實(shí)現(xiàn)代碼:

template void Delete[](T* pt){ int count = ((int*)pt)[-1]; for(int i = 0; i < count; i++)?????? pt[i].~T();?? void* p = (void*)((int)pt – 4);?? T::operator delete[](p);}

由此可見(jiàn),在默認(rèn)情況下operator new[]與operator new的行為是相同的,operator delete[]與operator delete也是,不同的是new operator與new[] operator、delete operator與delete[] operator。當(dāng)然,我們可以根據(jù)不同的需要來(lái)選擇重載帶有和不帶有“[]”的operator new和delete,以滿足不同的具體需求。

把前面類MyClass的代碼稍做修改——注釋掉析構(gòu)函數(shù),然后再來(lái)看看程序的輸出:

calling new[] with size=12 address=003A5A58

ctor

ctor

ctor

address of mc=003A5A58

這一次,new[]老老實(shí)實(shí)的申請(qǐng)了12個(gè)字節(jié)的內(nèi)存,并且申請(qǐng)的結(jié)果與new[] operator返回的結(jié)果也是相同的,看來(lái),是否在前面添加4個(gè)字節(jié),只取決于這個(gè)類有沒(méi)有析構(gòu)函數(shù),當(dāng)然,這么說(shuō)并不確切,正確的說(shuō)法是這個(gè)類是否需要調(diào)用構(gòu)造函數(shù),因?yàn)槿缦聝煞N情況下雖然這個(gè)類沒(méi)聲明析構(gòu)函數(shù),但還是多申請(qǐng)了4個(gè)字節(jié):一是這個(gè)類中擁有需要調(diào)用析構(gòu)函數(shù)的成員,二是這個(gè)類繼承自需要調(diào)用析構(gòu)函數(shù)的類。于是,我們可以遞歸的定義“需要調(diào)用析構(gòu)函數(shù)的類”為以下三種情況之一:

1 顯式的聲明了析構(gòu)函數(shù)的

2 擁有需要調(diào)用析構(gòu)函數(shù)的類的成員的

3 繼承自需要調(diào)用析構(gòu)函數(shù)的類的

類似的,動(dòng)態(tài)申請(qǐng)簡(jiǎn)單類型的數(shù)組時(shí),也不會(huì)多申請(qǐng)4個(gè)字節(jié)。于是在這兩種情況下,釋放內(nèi)存時(shí)使用delete或delete[]都可以,但為養(yǎng)成良好的習(xí)慣,我們還是應(yīng)該注意只要是動(dòng)態(tài)分配的數(shù)組,釋放時(shí)就使用delete[]。

釋放內(nèi)存時(shí)如何知道長(zhǎng)度

但這同時(shí)又帶來(lái)了新問(wèn)題,既然申請(qǐng)無(wú)需調(diào)用析構(gòu)函數(shù)的類或簡(jiǎn)單類型的數(shù)組時(shí)并沒(méi)有記錄個(gè)數(shù)信息,那么operator delete,或更直接的說(shuō)free()是如何來(lái)回收這塊內(nèi)存的呢?這就要研究malloc()返回的內(nèi)存的結(jié)構(gòu)了。與new[]類似的是,實(shí)際上在malloc()申請(qǐng)內(nèi)存時(shí)也多申請(qǐng)了數(shù)個(gè)字節(jié)的內(nèi)容,只不過(guò)這與所申請(qǐng)的變量的類型沒(méi)有任何關(guān)系,我們從調(diào)用malloc時(shí)所傳入的參數(shù)也可以理解這一點(diǎn)——它只接收了要申請(qǐng)的內(nèi)存的長(zhǎng)度,并不關(guān)系這塊內(nèi)存用來(lái)保存什么類型。下面運(yùn)行這樣一段代碼做個(gè)實(shí)驗(yàn):

char *p = 0;for(int i = 0; i < 40; i += 4){???char* s = new char[i];?? printf("alloc %2d bytes, address=%p distance=%d/n", i, s, s - p);?? p = s;}

我們直接來(lái)看VC2005下Release版本的運(yùn)行結(jié)果,DEBUG版因包含了較多的調(diào)試信息,這里就不分析了:

alloc0 bytes, address=003A36F0 distance=3815152

alloc4 bytes, address=003A3700 distance=16

alloc8 bytes, address=003A3710 distance=16

alloc 12 bytes, address=003A3720 distance=16

alloc 16 bytes, address=003A3738 distance=24

alloc 20 bytes, address=003A84C0 distance=19848

alloc 24 bytes, address=003A84E0 distance=32

alloc 28 bytes, address=003A8500 distance=32

alloc 32 bytes, address=003A8528 distance=40

alloc 36 bytes, address=003A8550 distance=40

每一次分配的字節(jié)數(shù)都比上一次多4,distance值記錄著與上一次分配的差值,第一個(gè)差值沒(méi)有實(shí)際意義,中間有一個(gè)較大的差值,可能是這塊內(nèi)存已經(jīng)被分配了,于是也忽略它。結(jié)果中最小的差值為16字節(jié),直到我們申請(qǐng)16字節(jié)時(shí),這個(gè)差值變成了24,后面也有類似的規(guī)律,那么我們可以認(rèn)為申請(qǐng)所得的內(nèi)存結(jié)構(gòu)是如下這樣的:

圖中不難看出,當(dāng)我們要分配一段內(nèi)存時(shí),所得的內(nèi)存地址和上一次的尾地址至少要相距8個(gè)字節(jié)(在DEBUG版中還要更多),那么我們可以猜想,這8個(gè)字節(jié)中應(yīng)該記錄著與這段所分配的內(nèi)存有關(guān)的信息。觀察這8個(gè)節(jié)內(nèi)的內(nèi)容,得到結(jié)果如下:

中右邊為每次分配所得的地址之前8個(gè)字節(jié)的內(nèi)容的16進(jìn)制表示,從圖中紅線所表示可以看到,這8個(gè)字節(jié)中的第一個(gè)字節(jié)乘以8即得到相臨兩次分配時(shí)的距離,經(jīng)過(guò)試驗(yàn)一次性分配更大的長(zhǎng)度可知,第二個(gè)字節(jié)也是這個(gè)意義,并且代表高8位,也就說(shuō)前面空的這8個(gè)字節(jié)中的前兩個(gè)字節(jié)記錄了一次分配內(nèi)存的長(zhǎng)度信息,后面的六個(gè)字節(jié)可能與空閑內(nèi)存鏈表的信息有關(guān),在翻譯內(nèi)存時(shí)用來(lái)提供必要的信息。這就解答了前面提出的問(wèn)題,原來(lái)C/C++在分配內(nèi)存時(shí)已經(jīng)記錄了足夠充分的信息用于回收內(nèi)存,只不過(guò)我們平常不關(guān)心它罷了。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C++
    C++
    +關(guān)注

    關(guān)注

    21

    文章

    2100

    瀏覽量

    73453
  • 變量
    +關(guān)注

    關(guān)注

    0

    文章

    613

    瀏覽量

    28306
  • 操作符
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    9030

原文標(biāo)題:深入C++的new

文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語(yǔ)言專家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    使用CY7C68013A芯片做了塊數(shù)據(jù)采集卡,拿到環(huán)境比較復(fù)雜的工業(yè)現(xiàn)場(chǎng)會(huì)出現(xiàn)設(shè)備丟失問(wèn)題怎么解決?

    大家好,我現(xiàn)在使用CY7C68013A芯片做了塊數(shù)據(jù)采集卡,在實(shí)驗(yàn)室環(huán)境運(yùn)行正常,但拿到環(huán)境比較復(fù)雜的工業(yè)現(xiàn)場(chǎng),就會(huì)出現(xiàn)板卡正常運(yùn)行過(guò)程中出現(xiàn)設(shè)備丟失問(wèn)題,設(shè)備丟失后且不能自動(dòng)恢復(fù),需要插拔
    發(fā)表于 02-29 07:27

    [原創(chuàng)]最復(fù)雜的冰箱電路

    做過(guò)50多款冰箱控制器,直到最近才做了款最全面的,發(fā)出來(lái)給大家共享一下,顯示板就不發(fā)了。電路不是很復(fù)雜,主要是程序比較復(fù)雜,多多指教。日后我會(huì)陸續(xù)帶來(lái)冰箱 洗衣機(jī) 空調(diào) 吸塵器 按摩
    發(fā)表于 09-21 15:11

    【CANNON試用體驗(yàn)】+了解一下新的工具stm32 cube mx

    這周比較忙,直沒(méi)有好好的分析好代碼,不過(guò)看了一下這個(gè)代碼,因?yàn)楦暗牟惶?b class='flag-5'>一樣就了解
    發(fā)表于 03-06 20:23

    關(guān)于AM335x以太網(wǎng)驅(qū)動(dòng)改寫(xiě),CPSW的代碼看過(guò)感覺(jué)比較復(fù)雜,有沒(méi)有個(gè)最小的DMA模式讀寫(xiě)以太網(wǎng)硬件的原始示例呢?

    (RAW Socket不可以使用)來(lái)讀寫(xiě),即雙網(wǎng)口的SK中的其中個(gè)以太網(wǎng)口是專門用于處理這些特殊的通訊,CPSW的代碼看過(guò)感覺(jué)比較復(fù)雜,有沒(méi)有個(gè)最小的DMA模式讀寫(xiě)以太網(wǎng)硬件的原
    發(fā)表于 06-04 00:58

    USB顯微鏡,不想了解一下嗎?

    USB顯微鏡,不想了解一下嗎? 近日,在很多國(guó)外網(wǎng)站上都搜的到配備了USB接口的數(shù)字顯微鏡,他們打破了傳統(tǒng)顯微鏡機(jī)體笨重,操作復(fù)雜的傳統(tǒng),
    發(fā)表于 04-28 10:24 ?3509次閱讀

    宏碁智能佛珠了解一下

    朋友,最近工作順利嗎?家庭幸福嗎?生活美滿嗎?看你心情復(fù)雜,要不要了解一下宏碁剛發(fā)布的智能佛珠?
    的頭像 發(fā)表于 08-08 16:27 ?3521次閱讀

    簡(jiǎn)單介紹一下Linux中ELF格式文件

    ELF(Executable and Linkable Format)即可執(zhí)行連接文件格式,是比較復(fù)雜的文件格式,但其應(yīng)用廣泛。
    發(fā)表于 04-27 19:09 ?2.7w次閱讀
    簡(jiǎn)單介紹<b class='flag-5'>一下</b>Linux中ELF格式文件

    電磁爐加熱一下就停一下什么原因及解決辦法

    電磁爐有時(shí)會(huì)出現(xiàn)加熱故障,現(xiàn)象是熱一下一下在熱一下又停一下,基本隔
    發(fā)表于 03-18 09:02 ?27.3w次閱讀

    plc編程中工程比較復(fù)雜的時(shí)候程序怎么編寫(xiě)

    梯形圖語(yǔ)言是種面向過(guò)程的程序設(shè)計(jì)語(yǔ)言,它脫胎于電氣控制圖,繼承了其簡(jiǎn)單、易懂的特點(diǎn)。但是面對(duì)復(fù)雜的工程時(shí)程序編寫(xiě)繁鎖,可讀性較差。這里引入軟件工程的些觀點(diǎn)給用戶在面向較復(fù)雜工程編程
    發(fā)表于 12-16 11:46 ?2475次閱讀

    了解一下AOC有源光纜的相關(guān)知識(shí)

    信息傳輸與交換量與日俱增,高密度,高寬帶應(yīng)用越來(lái)越多,傳統(tǒng)的銅質(zhì)電纜已經(jīng)不能適應(yīng)通信的需要,相比于傳統(tǒng)線纜來(lái)說(shuō),有源光纜能夠幫助通信設(shè)備享受到光傳輸?shù)木薮髢?yōu)勢(shì)。那AOC有源光纜有什么不同呢?今天就跟易天光通信(ETU-LINK)一起來(lái)了解
    的頭像 發(fā)表于 03-25 08:58 ?2235次閱讀

    大哥!100W移動(dòng)電源方案,了解一下

    100W移動(dòng)電源方案,了解一下!
    的頭像 發(fā)表于 06-02 19:03 ?2307次閱讀
    大哥!100W移動(dòng)電源方案,<b class='flag-5'>了解</b><b class='flag-5'>一下</b>

    了解一下光纖傳輸?shù)奶攸c(diǎn)

    光纖作為綜合布線種常見(jiàn)的傳輸媒介,很多人搞不清楚光纖和光纜這兩者,光纖是光傳導(dǎo)工具,傳輸原理是“光的全反射”,被用作長(zhǎng)距離的信息傳遞,下面跟著科蘭小編一起來(lái)了解一下光纖傳輸?shù)奶攸c(diǎn)。
    的頭像 發(fā)表于 06-08 13:42 ?778次閱讀

    虹科帶你來(lái)了解一下汽車以太網(wǎng)和TSN的測(cè)試標(biāo)準(zhǔn)

    虹科帶你來(lái)了解一下汽車以太網(wǎng)和TSN的測(cè)試標(biāo)準(zhǔn)
    的頭像 發(fā)表于 12-22 17:46 ?3310次閱讀
    虹科帶你來(lái)<b class='flag-5'>了解</b><b class='flag-5'>一下</b>汽車以太網(wǎng)和TSN的測(cè)試標(biāo)準(zhǔn)

    了解一下“薄膜厚度監(jiān)控”

    光學(xué)鍍膜干貨來(lái)了!了解一下“薄膜厚度監(jiān)控”
    的頭像 發(fā)表于 07-20 10:11 ?815次閱讀
    <b class='flag-5'>了解</b><b class='flag-5'>一下</b>“薄膜厚度監(jiān)控”

    無(wú)需電流采樣電阻的智能電機(jī)驅(qū)動(dòng)IC,不來(lái)了解一下么?

    無(wú)需電流采樣電阻的智能電機(jī)驅(qū)動(dòng)IC,不來(lái)了解一下么?
    的頭像 發(fā)表于 11-30 17:43 ?424次閱讀
    無(wú)需電流采樣電阻的智能電機(jī)驅(qū)動(dòng)IC,不來(lái)<b class='flag-5'>了解</b><b class='flag-5'>一下</b>么?