丨堆棧raiic里該如何管理資源_第1頁
丨堆棧raiic里該如何管理資源_第2頁
丨堆棧raiic里該如何管理資源_第3頁
丨堆棧raiic里該如何管理資源_第4頁
丨堆棧raiic里該如何管理資源_第5頁
已閱讀5頁,還剩12頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

C++標(biāo)準(zhǔn)里一個(gè)相關(guān)概念是自由區(qū),英文是 store,特指使用new和delete來new和delete操作的區(qū)域 操作的區(qū)域是但ew和delete通常底層使用malloc和 來實(shí)現(xiàn),所以 sore也是heap鑒于對(duì)其區(qū)分的實(shí)際意義并不大,在本專欄里,除非另有特殊說明,我會(huì)只使用堆這一術(shù)語。棧,英文是stack,在內(nèi)存管理的語境下,指的是函數(shù)調(diào)用過程中產(chǎn)生的本地變量和調(diào)用數(shù)據(jù)的區(qū)域。這個(gè)棧和數(shù)據(jù)結(jié)構(gòu)里的棧高度相似,都滿足“后進(jìn)先出”(last-in-first-out或RAII,完整的英文是ResourceAcquisitionIsInitialization,是C++所特有的資源管理方式。有少量其他語言,如D、Ada和Rust也采納了RAII,但主流的編程語言中,C++是唯一一個(gè)依賴RAII來做資源管理的。RAII依托棧和析構(gòu)函數(shù),來對(duì)所有的資源——包括堆內(nèi)存在內(nèi)——進(jìn)行管理。對(duì)RAII的使用,使得C++不需要類似于Java那樣的收集方法,也能有效地對(duì)內(nèi)存進(jìn)行管理。RAII的存在,也是收集雖然理論上可以在C++使用,但從來沒有真正流行過的主要原因。接下來,我將會(huì)對(duì)堆、棧和RAII堆//autoptr=new#lst=代//ArrayList<int>list=new從歷史的角度,動(dòng)態(tài)內(nèi)存分配實(shí)際上是較晚出現(xiàn)的。由于動(dòng)態(tài)內(nèi)存帶來的不確定性——內(nèi)存分配耗時(shí)需要多久?失敗了怎么辦?等等——至今仍有很多場合會(huì)禁用動(dòng)態(tài)內(nèi)存,尤其在實(shí)時(shí)性要求比較高的場合,如飛行控制器和電信設(shè)備。不過,由于大家多半對(duì)這種用法比較熟悉,特別是從C和C++以外的其他語言開始學(xué)習(xí)編程的程序員,所以提到內(nèi)存管在堆上分配內(nèi)存,有些語言可能使用new這樣的關(guān)鍵字,有些語言則是在對(duì)象的構(gòu)造時(shí)隱C++通常會(huì)做上面的操作1和2。Java會(huì)做上面的操作1和3。而Python會(huì)做上面的操作1、2、3。這是語言的特性方式?jīng)Q定的。第一,分配內(nèi)存要考慮程序當(dāng)前已經(jīng)有多少未分配的內(nèi)存。內(nèi)存不足時(shí)要從操作系統(tǒng)申請(qǐng)新的內(nèi)存。內(nèi)存充足時(shí),要從可用的內(nèi)存里取出一塊合適大小的內(nèi)存,做簿記工作將其標(biāo)記為已用,然后將其返回給要求內(nèi)存的代碼。第二,釋放內(nèi)存不只是簡單地把內(nèi)存標(biāo)記為未使用。對(duì)于連續(xù)未使用的內(nèi)存塊,通常內(nèi)存管理器需要將其合并成一塊,以便可以滿足后續(xù)的較大內(nèi)存分配要求。畢竟,目前的編程模式都要求申請(qǐng)的內(nèi)存塊是連續(xù)的。第三,收集操作有很多不同的策略方式,以實(shí)現(xiàn)性能、實(shí)時(shí)性、額外開銷等各方面的平衡。由于C++里通常都不使用收集,所以就不是我們專欄的重點(diǎn),不再展開講解。注意在圖1e的狀態(tài) 當(dāng)然,這只是一個(gè)簡單的示意,只是為了讓你能夠?qū)@個(gè)過程有一個(gè)大概的感性認(rèn)識(shí)。在不考慮收集的情況下,內(nèi)存需要手工釋放;在此過程中,內(nèi)存可能有碎片化的情況。比如,在圖1d的情況下,雖然總共剩余內(nèi)存為6,但卻滿足不了長度大于4的內(nèi)存分配要求。理器的任務(wù),一般情況下我們不需要介入。我們只需要正確地使用new和delete。每個(gè)new出來的對(duì)象都應(yīng)該用delete來釋放,就是這么簡單。delete是一種常見的情況,這叫“內(nèi)存泄漏”——相信你一定聽到過這void{bar*ptr=new…delete6deleteptr更重要的,這個(gè)代碼不符合C++的慣用法。在C++里,這種情況下有99%的可能性不應(yīng)該使用堆內(nèi)存分配,而應(yīng)使用棧內(nèi)存分配。這樣寫代碼的,估計(jì)可能是從Java轉(zhuǎn)過代代1bar*23…4try5ptr=new6…7}89{}13void{…bar*ptr=…delete}delete的可能性是不是大多了?有關(guān)這個(gè)問題的解決方法,我們?cè)谙乱缓茫盐覀儠簳r(shí)就討論到這兒。下面,我們看看更符合C棧我們先來看一段示例代碼,來說明C++里函數(shù)調(diào)用、本地變量是如何使用棧的。當(dāng)然,這一過程取決于計(jì)算機(jī)的實(shí)際架構(gòu),具體細(xì)節(jié)可能有所不同,但原理上都是相通的,都會(huì)使用一個(gè)后進(jìn)先出的結(jié)構(gòu)。代代123456789voidfoo(int{…}voidbar(int{inta=n+1;}int{……}在我們的示例中,棧是向上增長的。在包括86在內(nèi)的大部分計(jì)算機(jī)體系架構(gòu)中,棧的增長方向是低地址,因而上方意味著低地址。任何一個(gè)函數(shù),根據(jù)架構(gòu)的約定,只能使用進(jìn)入函數(shù)時(shí)棧指針向上部分的??臻g。當(dāng)函數(shù)調(diào)用另外一個(gè)函數(shù)時(shí),會(huì)把參數(shù)也壓入棧里(此處忽略使用寄存器傳遞參數(shù)的情況),然后把下一行匯編指令的地址壓入棧,并跳轉(zhuǎn)到新的函數(shù)。新的函數(shù)進(jìn)入后,首先做一些必須的保存工作,然后會(huì)調(diào)整棧指針,分配出本地變量所需的空間,隨后執(zhí)行函數(shù)中的代碼,并在執(zhí)行完畢之后,根據(jù)調(diào)用者壓入棧的地址,返回到調(diào)用者未執(zhí)行的代碼中繼續(xù)執(zhí)行。2語,叫做棧幀(stackframe)。GCC和Clang令行參數(shù)中提到frame的,如-前面例子的本地變量是簡單類型,C++里稱之為POD類型( inOldData)。對(duì)于有構(gòu)造和析構(gòu)函數(shù)的非POD類型,棧上的內(nèi)存分配也同樣有效,只不過C++編譯器會(huì)在異常時(shí)對(duì)析構(gòu)函數(shù)的調(diào)用,還有一個(gè)專門的術(shù)語,叫棧展開(stackunwinding)。事實(shí)上,如果你用MSVC編譯含異常的C++代碼,但沒有使用上一講的/EHsc參數(shù),編warningC4530:C++exceptionhandlerused,butunwindsemanticsarenotenabled.Specify/EHsc代代123456789#includeclass{Obj(){puts("Obj()");~Obj(){puts("~Obj()");voidfoo(int{Objif(n==throw"life,theuniverseand}int{try{}catch(constchar*{}}代代life,theuniverseand也就是說,不管是否發(fā)生了異常,objC*和&JavaPython一樣一個(gè)堆上的對(duì)象。對(duì)于像智能指針這樣的類型,你寫ptr->call()和ptr.get(),語法上都是對(duì)的,并且和有著不同的語法作用。而在大部分其他語言里,成員只用.,但在作用上實(shí)際等價(jià)于C++的->。這種值語義和語義的區(qū)CC++,就需要理解它的值語義C++的重要特性RAIIC++支持將對(duì)象在棧上面。但是,在很多情況下,對(duì)象不能,或不應(yīng)該,在棧enumclassshape_type…66789classshape{…classcircle:publicshape{…};classtriangle:publicshape{…};classrectangle:publicshape{…};shape*create_shape(shape_type{…switch(type)caseshape_type::circle:returnnewcircle(…);caseshape_type::triangle:returnnewtriangle(…);caseshape_type::rectangle:returnnewrectangle(…);…}}這個(gè)reate_hape方返回一個(gè)sape對(duì)象,對(duì)象的實(shí)際類型是某個(gè)shape的子類,圓啊,三角形啊,矩形啊,等等。這種情況下,函數(shù)的返回值只能是指針或其變體形式。如果返回類型是shape,實(shí)際卻返回一個(gè)ircle,編譯器不會(huì)報(bào)錯(cuò),但結(jié)果多半是錯(cuò)的。這種現(xiàn)象叫對(duì)象切片(obectsliing),是C++特有的一種編碼錯(cuò)誤。這種錯(cuò)誤不是語法錯(cuò)誤,而是一個(gè)對(duì)象相關(guān)的語義錯(cuò)誤,也算是C++的一個(gè)陷阱了,大家需要這個(gè)問題。那么,我們?cè)鯓硬拍艽_保,在使用create_shape答案就在析構(gòu)函數(shù)和它的棧展開行為上。我們只需要把這個(gè)返回值放到一個(gè)本地變量里,并classshape_wrapperexplicitshape*ptr=:ptr_(ptr){delete shape*get()const{returnptr_;}shape*void{…ptr_wrapper(…如果你好奇delete空指針會(huì)發(fā)生什么的話,那答案是,這是一個(gè)合法的空操作。在一個(gè)對(duì)象和delete////new{void*temp=operatortrycircle*ptrreturn catch(...)operator 14if(ptr!=nullptr)operator4也就是說,new的時(shí)候先分配內(nèi)存(bad_alloc),然后在這個(gè)結(jié)果指針上構(gòu)造對(duì)象(合法的C++代碼);構(gòu)造成功則newshape_wrapperRAII代1std::mutex23void4 //7代1std::mutex23void4 //做需要同步的工作////10本講我們討論了C++里內(nèi)存管理的一些基本概念,強(qiáng)調(diào)棧是C++里最“自然”的內(nèi)存使用方式,并且,使用基于棧和析構(gòu)函數(shù)的RAII,可以有效地對(duì)包括堆內(nèi)存在內(nèi)的系統(tǒng)資源Wikipedia,“MemoryWikipedia,“Stack-basedmemoryWikipedia,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論