新聞中心
資源管理

目前創(chuàng)新互聯(lián)公司已為上千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁(yè)空間、網(wǎng)站托管維護(hù)、企業(yè)網(wǎng)站設(shè)計(jì)、班戈網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。
在C語(yǔ)言中,資源管理是一個(gè)極為繁瑣易錯(cuò)的工作,大多復(fù)雜的C系統(tǒng)都面臨著內(nèi)存泄露、懸掛指針等問(wèn)題。這是一方面是由底層語(yǔ)言的特點(diǎn)決定;另一方面也是由于C語(yǔ)言特性相對(duì)較少,嚴(yán)重依賴程序員進(jìn)行正確的資源管理,缺乏有效的支持手段。
C#和C++兩門(mén)語(yǔ)言的定位不同,它們?cè)谫Y源管理方面采取了兩種截然不同的方式:一為GC,一為RAII。GC讓程序建立在更高的抽象層次上,使資源管理變得更方便,更安全;而RAII則保留了C的底層能力,同時(shí)在C++特性的支持下提供了簡(jiǎn)單有效的資源管理方式。我們知道C++最激烈的批評(píng)往往來(lái)自于C社區(qū),而在我看來(lái)C程序員可以不接受虛函數(shù),不接受模板,但有什么理由不接受RAII呢?可以說(shuō)RAII是C++相對(duì)C來(lái)說(shuō)幾乎無(wú)副作用的明顯進(jìn)步。
下面就從GC開(kāi)始:
引用代替指針
C#通過(guò)CLR管理托管內(nèi)存,用引用抽象代替指針間接操作托管內(nèi)存,讓程序員在更高的層次上安全地使用資源。這使得C#失去了直接管理內(nèi)存的能力,但換來(lái)了以下好處:
1.類(lèi)型安全:在C/C++中可以通過(guò)類(lèi)型轉(zhuǎn)換把整數(shù)或其他類(lèi)型的指針轉(zhuǎn)換為特定類(lèi)型的指針,這意味著指針是非類(lèi)型安全的,必須由程序員來(lái)保證指針代表的內(nèi)存空間的合法性。而C#引用可以看作是類(lèi)型安全的指針,as運(yùn)算符可以保證轉(zhuǎn)換的類(lèi)型安全性。
2.內(nèi)存整理:創(chuàng)建對(duì)象需要從堆中動(dòng)態(tài)分配連續(xù)的內(nèi)存空間,由于不同對(duì)象的內(nèi)存大小是不同的,常見(jiàn)的首次匹配和最優(yōu)匹配堆分配算法都會(huì)造成堆中的內(nèi)存碎片問(wèn)題。碎片的存在使實(shí)際可用內(nèi)存小于物理內(nèi)存,所以應(yīng)盡量減少碎片的產(chǎn)生。一個(gè)方向是設(shè)計(jì)更好的內(nèi)存分配算法;另一個(gè)方向是通過(guò)周期性地進(jìn)行內(nèi)存整理調(diào)整優(yōu)化。在C/C++中,由于指針代表了絕對(duì)地址,因此不存在通用的內(nèi)存整理算法;而C#屏蔽了指針,通過(guò)引用操作對(duì)象,就使得內(nèi)存整理成為可能。PS:這并不意味著C/C++內(nèi)存分配就弱于C#,C/C++程序可以為某種類(lèi)型的對(duì)象設(shè)計(jì)專用的內(nèi)存分配方式,甚至把對(duì)象指定分配到某一物理地址空間,這些都是C#不具備的。
托管和非托管資源
在C#中,資源分為托管資源和非托管資源兩種。GC在回收無(wú)用對(duì)象資源時(shí),可以自動(dòng)回收托管資源(比如托管內(nèi)存),但對(duì)于非托管資源(比如Socket、文件、數(shù)據(jù)庫(kù)連接)必須在程序中顯式釋放。
托管資源的回收首先需要GC識(shí)別無(wú)用對(duì)象,然后回收其資源。一般無(wú)用對(duì)象是指通過(guò)當(dāng)前的系統(tǒng)根對(duì)象和調(diào)用堆棧對(duì)象不可達(dá)的對(duì)象。對(duì)象有一個(gè)重要的特點(diǎn)導(dǎo)致無(wú)用對(duì)象判斷的復(fù)雜性:對(duì)象間的相互引用!如果沒(méi)有相互引用,就可以通過(guò)“引用計(jì)數(shù)”這種簡(jiǎn)單高效的方式實(shí)現(xiàn)無(wú)用對(duì)象的判斷,并實(shí)現(xiàn)實(shí)時(shí)回收。正是由于相互引用的存在導(dǎo)致GC需要設(shè)計(jì)更為復(fù)雜的算法,這樣帶來(lái)的最大問(wèn)題在于喪失了資源回收的實(shí)時(shí)性,而變成一種不確定的方式。
對(duì)于非托管資源的釋放,C#提供了兩種方式:
1.Finalizer:寫(xiě)法貌似C++的析構(gòu)函數(shù),本質(zhì)上卻相差甚遠(yuǎn)。Finalizer是對(duì)象被GC回收之前調(diào)用的終結(jié)器,初衷是在這里釋放非托管資源,但由于GC運(yùn)行時(shí)機(jī)的不確定性,通常會(huì)導(dǎo)致非托管資源釋放不及時(shí)。另外,F(xiàn)inalizer可能還會(huì)有意想不到的副作用,比如:被回收的對(duì)象已經(jīng)沒(méi)有被其他可用對(duì)象所引用,但Finalizer內(nèi)部卻把它重新變成可用,這就破壞了GC垃圾收集過(guò)程的原子性,增大了GC開(kāi)銷(xiāo)。
2.Dispose Pattern:C#提供using關(guān)鍵字支持Dispose Pattern進(jìn)行資源釋放。這樣能通過(guò)確定的方式釋放非托管資源,而且using結(jié)構(gòu)提供了異常安全性。所以,一般建議采用Dispose Pattern,并在Finalizer中輔以檢查,如果忘記顯式Dispose對(duì)象則在Finalizer中釋放資源。
可以說(shuō),GC為程序帶來(lái)安全方便的同時(shí)也付出了不小的代價(jià):一則喪失了托管資源回收的實(shí)時(shí)性,這在實(shí)時(shí)系統(tǒng)和資源受限系統(tǒng)中是致命的;二則沒(méi)有把托管資源和非托管資源的管理統(tǒng)一起來(lái),造成概念割裂。C++的定位之一是底層開(kāi)發(fā)能力,所以不難理解GC并沒(méi)有成為C++的語(yǔ)言特性。雖然我們?cè)贑++0x和各種第三方庫(kù)都能看到GC的身影,但GC對(duì)于C++來(lái)講并不是那么重要,至多是一個(gè)有益的補(bǔ)充。C++足以傲視C,并和C# GC一較高下的是它的RAII。
棧語(yǔ)義
在介紹RAII之前,讓我們先來(lái)看一道C++面試題:“重構(gòu)下面的代碼,在保證正確釋放資源的情況下,去掉多余的try catch”
- //C++
- void f(){
- try{
- int *ptr = new int(123);
- …//do something with ptr
- delete ptr;
- }
- catch {
- delete ptr;
- }
- }
代碼中new int在堆上分配內(nèi)存,并通過(guò)delete小心翼翼地釋放內(nèi)存。這是典型的C風(fēng)格的C++代碼,雖然用了try、catch等高級(jí)語(yǔ)法,但資源管理方式依舊是C。按C++特有的方式可以重構(gòu)成這樣:
- //C++資源管理方式
- //定義資源代理類(lèi)模板
- template
- class Resource{
- public:
- Resource(T *ptr) { this->ptr = ptr;} //構(gòu)造函數(shù)中初始化資源
- ~Resource() { delete ptr; } //析構(gòu)函數(shù)中釋放資源
- T& operator*() { return *ptr; } //重載*運(yùn)算符
- T* operator->() { return ptr; } //重載->運(yùn)算符
- //…省略了拷貝構(gòu)造函數(shù)和賦值運(yùn)算符等
- private:
- T* ptr;
- };
- void f(){
- Resource r(new int(123));
- //do something with r
- }
f函數(shù)中,我們?cè)跅I蟿?chuàng)建了一個(gè)資源模板類(lèi)Resource的對(duì)象r,并通過(guò)r來(lái)提供服務(wù)。只是這么簡(jiǎn)單的一包裝,就省掉了繁瑣易錯(cuò)的try,catch,不管f內(nèi)部出什么問(wèn)題,拋什么異常,都能保證r所管理的內(nèi)存資源最終被正確釋放。C++保證一旦離開(kāi)詞法作用域,在任何情況下都會(huì)調(diào)用棧上對(duì)象的析構(gòu)函數(shù),這就是所謂的“棧語(yǔ)義”(stack semantics)。事實(shí)上,STL已經(jīng)有auto_ptr這個(gè)智能指針類(lèi)模板,其實(shí)現(xiàn)和上面的Resource類(lèi)模板類(lèi)似。
RAII
RAII是resource acquisition is initialization的縮寫(xiě),意為“資源獲取即初始化”。它是C++之父Bjarne Stroustrup提出的設(shè)計(jì)理念,其核心是把資源和對(duì)象的生命周期綁定,對(duì)象創(chuàng)建獲取資源,對(duì)象銷(xiāo)毀釋放資源。在RAII的指導(dǎo)下,C++把底層的資源管理問(wèn)題提升到了對(duì)象生命周期管理的更高層次。上面的例子,我們把new所獲取的內(nèi)存塊視為資源,把r對(duì)象視為資源的代理對(duì)象,r應(yīng)負(fù)責(zé)資源的獲取和釋放。在棧語(yǔ)義和操作符重載的支持下,C++的RAII體現(xiàn)出了簡(jiǎn)潔、安全、實(shí)時(shí)的特點(diǎn):
1.概念簡(jiǎn)潔性:讓資源(包括內(nèi)存和非內(nèi)存資源)和對(duì)象的生命周期綁定,資源類(lèi)的設(shè)計(jì)者只需用在類(lèi)定義內(nèi)部處理資源問(wèn)題,提高了程序的可維護(hù)性
2.類(lèi)型安全性:通過(guò)資源代理對(duì)象包裝資源(指針變量),并利用運(yùn)算符重載提供指針運(yùn)算方便使用,但對(duì)外暴露類(lèi)型安全的接口
3.異常安全性:棧語(yǔ)義保證對(duì)象析構(gòu)函數(shù)的調(diào)用,提高了程序的健壯性
4.釋放實(shí)時(shí)性:和GC相比,RAII達(dá)到了和手動(dòng)釋放資源一樣的實(shí)時(shí)性,因此可以承擔(dān)底層開(kāi)發(fā)的重任
也許你還在驚訝RAII如此簡(jiǎn)單的時(shí)候,關(guān)于RAII的主要內(nèi)容已經(jīng)介紹完了。簡(jiǎn)單不意味著簡(jiǎn)陋,在我看來(lái)RAII雖然不像GC一樣,是一套具體的機(jī)制,但它蘊(yùn)含的對(duì)象與資源關(guān)系的哲學(xué)深度的理解卻使得我對(duì)Bjarne Stroustrup肅然起敬!
最后,不得不提醒RAII的理念固然簡(jiǎn)單,不過(guò)在具體實(shí)現(xiàn)的時(shí)候仍有需要小心的地方。比如對(duì)于STL的auto_ptr,可以視為資源的代理對(duì)象,auto_ptr對(duì)象間的賦值是一個(gè)需要特別注意的地方。簡(jiǎn)單說(shuō)來(lái)資源代理對(duì)象間賦值的語(yǔ)義不滿足“賦值相等”,其語(yǔ)義是資源管理權(quán)的轉(zhuǎn)移。
什么是“賦值相等”呢?比如:
int a;
int b = 10;
a = b; //這句話執(zhí)行后 a == b
但對(duì)于資源代理對(duì)象,這是不滿足的,比如:
auto_ptr a(null);
auto_ptr b(new int(123));
a = b; //這句話執(zhí)行后a != b,賦值的語(yǔ)義是b把資源的管理權(quán)交給了a
C#與C++資源管理方式對(duì)陣總結(jié)
對(duì)比介紹了C#和C++資源管理方式:GC和RAII。
本文來(lái)自Todd Wei的博客園文章《C# vs C++之二:GC vs RAII 》
當(dāng)前文章:C#與C++資源管理方式對(duì)陣GC對(duì)比RAII
標(biāo)題鏈接:http://www.fisionsoft.com.cn/article/cocodps.html


咨詢
建站咨詢
