欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
C++內存管理變革

引言

C/C++語(yǔ)言的內存管理經(jīng)歷了幾次變革,但至今仍未能趨于成熟。這幾次變革主要包括:

1.         從malloc/free到new/delete。這場(chǎng)變革是OOP技術(shù)興起的產(chǎn)物。C++是強類(lèi)型語(yǔ)言,new/delete的主要成果也就是加強了類(lèi)型觀(guān)念,減少了強制類(lèi)型轉換的需求。但是從內存管理角度看,這個(gè)變革并沒(méi)有多少的突破性。

2.         從new/delete到內存配置器(allocator)。自從STL被納入C++標準庫后,C++世界產(chǎn)生了巨大的變化。而從內存管理角度來(lái)看,allocator的引入也是C++內存管理一個(gè)突破。留意一下你就可以發(fā)現,整個(gè)STL所有組件的內存均從allocator分配。也就是說(shuō),STL并不推薦使用new/delete進(jìn)行內存管理,而是推薦使用allocator。

然而,STL的allocator并沒(méi)有導致C++語(yǔ)言在內存管理上發(fā)生巨大的變化。除了STL本身外,并沒(méi)有多少人使用allocator,甚至是意識到allocator的重要性。所以C++程序員在使用STL的同時(shí),依舊在使用new/delete進(jìn)行煩瑣的內存分配/釋放過(guò)程。

究其原因,主要有二。一是allocator的引入,STL設計者主要可能還是出于將內存管理從容器的實(shí)現獨立出來(lái)的設計理念作用,讓STL使用者在內存管理算法上有選擇的余地。設計者本身都可能也沒(méi)有意識到allocator的重要性。二是allocator本身也只是側重于關(guān)注效率上,而沒(méi)有側重于C++語(yǔ)言使用者對內存管理觀(guān)念的變革上。

總之,在我看來(lái),STL的引入allocator,是一件了不起的事情。但是這場(chǎng)變革被忽視了,沒(méi)有得到貫徹。當然,這也與STL的allocator本身的缺陷有關(guān)。

本文要討論的,正是如何貫徹STL的allocator思想,對其進(jìn)行適當的改進(jìn),以期在C++內存管理觀(guān)念上產(chǎn)生變革性的突破,徹底淘汰傳統的new/delete內存管理方法[1]。

垃圾回收器

幾乎所有目前流行的垃圾回收器,均傾向于將使用者當作一個(gè)傻瓜,期望能夠讓使用者在完全不理解內存管理的情況下,可以很好的使用它。應該說(shuō)這它們基本上都也做到了(雖然使用者有時(shí)也有這樣那樣的煩惱,但總體來(lái)說(shuō)情況確實(shí)得到了很大程度的改善)。然而這一設計理念我并不十分認同。

首先,可以在一個(gè)提供垃圾回收器的語(yǔ)言中自如的工作,沒(méi)有被垃圾回收器所困擾,本身已經(jīng)是很了不起的事情,他們絕對是非常聰明的人,而不是傻瓜。他們理解垃圾回收器的工作原理,選擇它并且讓它為他們工作,只是因為還有更重要的事情等著(zhù)他們去做。必要的時(shí)候,他們需要有辦法控制垃圾回收器,使它按照他們的意愿工作。因此,垃圾回收器的設計要點(diǎn)在于把使用者從煩瑣的內存管理中解脫出來(lái),使得他們可以將全部精力投入到本身的業(yè)務(wù)邏輯上,而不是讓垃圾回收器看起來(lái)更傻瓜式。

其次,使用一個(gè)全自動(dòng)的垃圾回收器,在內存回收的時(shí)機不明確的情況下,垃圾回收器的工作過(guò)程有很大的不確定性,這給使用者帶來(lái)煩惱。例如C#在調用非管制代碼(如調用Win32 api)時(shí),這些問(wèn)題變得突出。一個(gè)不小心,就有可能出現Win32api還在使用一塊內存,而垃圾回收器已經(jīng)把它回收了的情形。在小心翼翼的避開(kāi)這些陷阱時(shí),這種感覺(jué)其實(shí)與C/C++程序員遺憾語(yǔ)言沒(méi)有垃圾回收器的感覺(jué)有點(diǎn)類(lèi)似。

因此,最理想的情況,是內存管理器提供垃圾回收的能力,但是它也只是提供這個(gè)能力而已,至于什么時(shí)候進(jìn)行垃圾回收,完全可以由用戶(hù)自己控制。另外,用戶(hù)也可以強制釋放一塊內存,而不是完全被動(dòng)的等待垃圾回收過(guò)程決策何時(shí)回收該內存。對于客戶(hù)來(lái)說(shuō),他有權掌控一切,只是如果萬(wàn)一他確實(shí)疏忽了,垃圾回收器能夠為他護航。

將垃圾回收器引入C++,有沒(méi)有這種可能呢?我認為,如果我們試圖提供一個(gè)全自動(dòng)的垃圾回收器,這相當困難。我們看到以Microsoft之能,仍然無(wú)法把這件事做好[2]?;蛟S,我們需要改變一下觀(guān)念:一個(gè)半自動(dòng)的垃圾回收器,也許就可能可以和C++融洽相處了呢?

初識allocator

allacator中文稱(chēng)為“內存配置器”,通常它是一個(gè)類(lèi),負責提供內存管理(可能包含內存分配、釋放、自動(dòng)回收等能力)相關(guān)的服務(wù)。例如,我們通過(guò)C提供的malloc/free即刻提供一個(gè)allocator實(shí)作出來(lái):

class SimpleAlloc
{
public:
    
//注意這里提供的參數fnDestroy,它是為那些具備垃圾回收能力的allocator需要提供。
void* Alloc(size_t cb, FnDestructor fnDestroy = NULL)
    {
        
return malloc(cb);
    }
 
    
//注意這里有看似多余的參數cb,這完全是為了和后續提供的allocator規格一致的需要。
    void Free(void* data, size_t cb)
    {
        free(data);
    }
};

有了allocator,我們可以申請內存了,但是我們還不能用它創(chuàng )建一個(gè)C++對象。為了方便創(chuàng )建C++對象,我們提供了輔助的New操作,原型大體如下:

template <class Type, class AllocType>
Type
* New(AllocType& alloc);                    // 類(lèi)似于new Type
 
template 
<class Type, class ArgType1, class AllocType>
Type
* New(ArgType1 arg1, AllocType& alloc); // 類(lèi)似于new Type(arg1)
 
template 
<class Type, class AllocType>
Type
* NewArray(size_t count, AllocType& alloc);// 類(lèi)似于new Type[count]

有了這些輔助函數,我們就可以創(chuàng )建對象了。使用樣例:

SimpleAlloc alloc;
int* intArray = NewArray<int>(count, alloc);
MyClass
* obj = New<MyClass>(alloc);
MyClass
* objWithArg = New<MyClass>(arg1, alloc);
MyClass
* objArray = NewArray<MyClass>(count, alloc);

這里我們雖然使用SimpleAlloc創(chuàng )建對象,但是需要提醒的是,這些New操作對所有的allocator有效。如果你關(guān)心New函數的代碼,先不急,下面我們馬上就可以看到了。但是首先我們要繼續討論一下allocator。

allocator引起的觀(guān)念變化

接觸allocator,你可以體會(huì )到了它與C++傳統的new/delete觀(guān)念的不同。這主要有以下幾點(diǎn):

1.         每個(gè)類(lèi)(或者算法)本身,均有最合適它的內存管理機制,并不是向C++傳統的做法那樣,使用一個(gè)全局的new/delete。也許你會(huì )說(shuō),C++不也允許一個(gè)類(lèi)定義自己的new和delete嗎?是的,C++的確支持類(lèi)定義自己的new/delete,但注意,它的理念和allocator完全不同。我不認為它是C++的一個(gè)優(yōu)秀之作,相反,它起到了誤導作用。

因為,決定一個(gè)類(lèi)對象怎么去new出來(lái),并不是取決于該類(lèi)本身,而相反是取決于使用該類(lèi)的人。一個(gè)類(lèi)不需要關(guān)心自身被如何創(chuàng )造出來(lái),更不能假定。它需要關(guān)心的是它自己的類(lèi)成員如何被創(chuàng )建出來(lái),它的算法(你可以把類(lèi)看做一個(gè)算法集合)涉及到的所有組件如何被創(chuàng )建出來(lái)。而這,才是allocator帶來(lái)的觀(guān)念。

讓各種各樣的allocator創(chuàng )建同一個(gè)類(lèi)的不同實(shí)例,這些實(shí)例甚至可能在一起工作,相互協(xié)作。從STL的角度講,這完全是最正常不過(guò)的事情了。

2.         重要的是由allocator創(chuàng )建管理對象,避免在你的代碼中使用new/delete。如果可能,你可以如STL那樣,將allocator作為模板參數,不綁定具體的某個(gè)內存管理器。但是,如果你的算法依賴(lài)了某個(gè)allocator的實(shí)現特有的功能,這也并不要緊。你的目的不是要做到allocator的可替換,不是嗎?重要的是使用了這個(gè)allocator了,它給你在內存管理上帶來(lái)了益處。

但是,應該看到,STL實(shí)作的各種allocator,目前來(lái)看除了最簡(jiǎn)單使用malloc/free實(shí)現的外,主要就是基于mempool技術(shù)。而該技術(shù)的目標,不是讓內存使用者更加方便有效地進(jìn)行內存管理,而更多的是關(guān)注于內存分配的時(shí)間性能。為了讓C++程序員從內存管理中解脫出來(lái),我們需要實(shí)作新的alloctor,需要新的突破!

新視角:具垃圾回收能力的Allocator

對,我設想的一個(gè)做法是,貫徹STL的allocator觀(guān)念,并且提供具備特定的內存管理能力(例如垃圾回收)的各種allocator。讓C++社區廣泛接受allocator觀(guān)念,并且從中受益。C++程序員是時(shí)候拋棄傳統的new/delete,讓他們退出歷史舞臺了。

我接下來(lái)會(huì )實(shí)作兩個(gè)具體的allocator(均屬原創(chuàng ))。相信它們會(huì )讓你耳目一新,讓你不禁想到:哦,原來(lái)在C++中,我還可以這樣進(jìn)行內存管理。

當然,我最大的希望就是,這兩個(gè)allocator能夠起到拋磚引玉的作用,讓大家也清楚地意識到allocator的重要性,可以出現更多的具備各種能力的allocator,解脫C++程序員一直以來(lái)的苦難(可能是最大苦難[3])。

這兩個(gè)allocator均具備一定程度的垃圾回收能力。只是觀(guān)念上各有各的側重。我們接下來(lái)會(huì )分為兩個(gè)專(zhuān)題專(zhuān)門(mén)對它們進(jìn)行闡述。

輔助的New過(guò)程

我們終于可以開(kāi)始討論前文提到的New函數的實(shí)現上了。以不帶參數的New為例,它的代碼如下,可能并沒(méi)有你想象的那么復雜:

#include <new>
 
template 
<class Type, class AllocType>
inline Type
* New(AllocType& alloc)
{
    
void* obj = alloc.Alloc(sizeof(Type), DestructorTraits<Type>::Destruct);
    
return new(obj) Type;
}

其中DestructorTraits是一個(gè)根據類(lèi)型Type萃取[4]析構函數的萃取器。它看起來(lái)是這樣的:

template <class Type>
struct DestructorTraits
{
    
static void Destruct(void* pThis)
    {
        ((Type
*)pThis)->~Type();
    }
};

這樣,你就可以通過(guò)以下代碼new出對象了:

MyClassA* obj = New<MyClassA>(alloc);
MyClassB
* obj = New<MyClassB>(alloc);

特別提醒:這里New函數在VC++6.0下編譯通過(guò),但是產(chǎn)生的執行代碼存在嚴重bug。如果你只New一類(lèi)對象,沒(méi)有問(wèn)題,但在New了多種對象后,似乎VC++對MyClassA、MyClassB 兩者混淆起來(lái)了。為了支持VC++ 6.0,你需要對這里的New做出調整(關(guān)于這一點(diǎn),詳細請參考:VC++ 6.0小技巧)。

COM技術(shù)[5]與內存管理

已經(jīng)準備結束這篇短文的時(shí)候,忽然想到了長(cháng)久以來(lái)使用COM技術(shù)形成的一些感想,這些想法恰恰與內存管理緊密相關(guān)。故此想就這個(gè)問(wèn)題陳述一下。

從COM的IUnknown接口看,它主要關(guān)注兩個(gè)問(wèn)題:一個(gè)是QueryInterface,一個(gè)是引用計數(AddRef/Release)。COM組件很講究信息的屏蔽,使用者對組件的認識有限,這就給組件升級、擴充功能提供了可能。QueryInterface是一個(gè)很好的概念,需要發(fā)揚光大。

COM的引用計數則關(guān)注的是組件的生命期維護問(wèn)題。換句話(huà)說(shuō),就是組件如何銷(xiāo)毀的問(wèn)題。誠然,組件對象的銷(xiāo)毀問(wèn)題,是內存管理的關(guān)鍵。無(wú)論是COM的引用計數,還是垃圾回收技術(shù),均是要解決對象的銷(xiāo)毀問(wèn)題。只是兩者的側重點(diǎn)不太一樣,COM引用計數更關(guān)注“確保組件不會(huì )被提前銷(xiāo)毀了,確保組件訪(fǎng)問(wèn)的安全性”,而垃圾回收器則關(guān)注“不管怎樣確保組件最終被銷(xiāo)毀,沒(méi)有內存泄漏”。

在COM中,確保組件訪(fǎng)問(wèn)的安全性(避免非法訪(fǎng)問(wèn)),這個(gè)觀(guān)點(diǎn)太重要了,以至于它甚至不惜加重程序員的內存管理負擔。所以,在COM程序中,出現內存泄漏太正常了,而且一旦泄漏通常就是大片大片內存的漏。更加要命的是,你甚至不能有一個(gè)很簡(jiǎn)單有效的方法確認這個(gè)泄漏是由于哪段代碼引起。因為組件所有的客戶(hù)都是平等的,任何一個(gè)客戶(hù)代碼存在問(wèn)題均將導致內存的泄漏。

剛開(kāi)始接觸COM技術(shù)的時(shí)候,我對引用計數持的是比較正面的態(tài)度。但是隨著(zhù)部門(mén)逐步加大COM技術(shù)的使用力度后,四五年下來(lái),我漸漸開(kāi)始迷惑起來(lái)。一切并不如想象的那樣。這個(gè)引用計數的背后,需要我們付出多少額外的代價(jià)!

而這個(gè)迷惑、思索,可能就是本文以及后續相關(guān)內容的成因吧。

C++內存管理變革(2):最袖珍的垃圾回收器

概述

C/C++最被人詬病的,可能是沒(méi)有一個(gè)內存垃圾回收器(確切是說(shuō)沒(méi)有一個(gè)標準的垃圾回收器)。本文討論的內容要點(diǎn)是,在C/C++中實(shí)現一個(gè)最袖珍的、功能受限的垃圾回收器。這個(gè)垃圾回收器區別于其他垃圾回收器的主要特征是:

1.         袖珍但具實(shí)用性。整個(gè)垃圾回收器代碼行數100行左右(不含空白行),相當小巧。相對而言,它的功能也受到一定的限制。但是它在很多關(guān)鍵的場(chǎng)合恰恰非常有用。該垃圾回收器以實(shí)用作為首要目標,已經(jīng)成為我和身邊一些同事編程的重要工具?!?/p>

2.         高性能。區別于其他垃圾回收器的是這個(gè)袖珍的垃圾回收器非但不會(huì )導致性能的下降,反而提高了程序的時(shí)間性能(分配的速度加快)和空間性能(所占內存空間比正常的malloc/new少)。而這也是實(shí)用的重要指標。

本文算法并不復雜。技術(shù)上的東西,很多點(diǎn)明了就沒(méi)有什么了,也許重要的意義是在于其首創(chuàng )性。其實(shí),boost[1]提供的pool組件也在試圖提供類(lèi)似功能的自動(dòng)內存回收能力。但是實(shí)現相對復雜且低效(基于經(jīng)典的mempool技術(shù)[2])。

現在,你也許急著(zhù)想看看,這個(gè)垃圾回收器長(cháng)什么樣了。閑話(huà)少敘,那就讓我們就開(kāi)始一步步把謎底揭開(kāi)吧。

思路

理解該垃圾回收器的關(guān)鍵點(diǎn)在于,是在于理解它的目標:為一個(gè)復雜的局部過(guò)程(算法)提供自動(dòng)內存回收的能力?!?/p>

所謂局部過(guò)程(算法),是指那些算法復雜性較高,但在程序運行期所占的時(shí)間又比較短暫的過(guò)程[3]。例如:搜索引擎的搜索過(guò)程、讀盤(pán)/存盤(pán)過(guò)程、顯示(繪制)過(guò)程等等。通常這些過(guò)程可能需要申請很多內存,而且內存分配操作的入口點(diǎn)很多(就是調用new的地方很多),如果每調用一次new就要考慮應該在什么地方delete就徒然浪費我們寶貴的腦力,使得我們無(wú)法把全力精力集中在算法本身的設計上。也許就是在這種情形下,C/C++程序員特別羨慕那些具備垃圾回收器的語(yǔ)言。相對而言,如果算法復雜性不高的話(huà),我們的程序員完全有能力控制好new/delete的匹配關(guān)系。并且,這種“一切皆在我掌控之中”的感覺(jué)給了我們安全感[4]和滿(mǎn)足感?!?/p>

因此,這個(gè)垃圾回收器的重心并不是要提供一個(gè)理論上功能完備的內存自動(dòng)回收機制。它只是針對復雜性較高的局部過(guò)程(算法),為他們提供最實(shí)效的內存管理手段。從局部過(guò)程的一開(kāi)始,你就只管去申請、使用內存,等到整個(gè)算法完成之后,這個(gè)過(guò)程申請的大部分內存(需要作為算法結果保留的例外),無(wú)論它是在算法的那個(gè)步驟申請的,均在這個(gè)結束點(diǎn)上由垃圾回收器自動(dòng)銷(xiāo)毀。我們畫(huà)個(gè)示意圖:

圖 1

 

規格

我們將該垃圾回收器命名為AutoFreeAlloc。它的接口很簡(jiǎn)單,僅涉及兩個(gè)概念:Alloc、Clear。

typedef void (*FnDestructor)(void* pThis);
 
class AutoFreeAlloc
{
public:
    
~AutoFreeAlloc();                           // 析構函數。自動(dòng)調用Clear釋放內存
    void* Alloc(size_t cb);                     // 類(lèi)似于malloc(cb)
    void* Alloc(size_t cb, FnDestructor fn);    // 申請內存并指定析構函數
    void Clear();                               // 析構并釋放所有分配的對象
};

為了方便,提供輔助的New操作(上一篇中已經(jīng)簡(jiǎn)單介紹實(shí)現了),大體如下:

template <class Type, class AllocType>
Type
* New(AllocType& alloc);                    // 類(lèi)似于new Type
 
template 
<class Type, class ArgType1, class AllocType>
Type
* New(ArgType1 arg1, AllocType& alloc); // 類(lèi)似于new Type(arg1)
 
template 
<class Type, class AllocType>
Type
* NewArray(size_t count, AllocType& alloc);// 類(lèi)似于new Type[count]

使用樣例:

AutoFreeAlloc alloc;
 
int* intArray = (int*)alloc.Alloc(sizeof(int)*count);
int* intArray2 = NewArray<int>(count, alloc);
 
MyClass
* obj = New<MyClass>(alloc);
MyClass
* objWithArg = New<MyClass>(arg1, alloc);
MyClass
* objArray = NewArray<MyClass>(count, alloc);
 
alloc.Clear();
 
// …
// 現在,不能再訪(fǎng)問(wèn)intArray, obj, objWithArg, objArray等數據了。

內存管理機制

class AutoFreeAlloc
{
public:
    
enum { BlockSize = 2048 };
private:
    
struct _MemBlock
    {
        _MemBlock
* pPrev;
        
char buffer[BlockSize];
    };
    
enum { HeaderSize = sizeof(_MemBlock) - BlockSize };
    
    
char* m_begin;
    
char* m_end;
};

AutoFreeAlloc類(lèi)與內存管理相關(guān)的變量只有兩個(gè):m_begin、m_end。單從變量定義來(lái)看,基本上很難看明白。但是有了下面這張示意圖就容易理解多了:

圖 2

整個(gè)AutoFreeAlloc申請的內存,通過(guò)_MemBlock構成鏈表。只要獲得了鏈表的頭,就可以遍歷整個(gè)內存鏈,釋放所有申請的內存了。而鏈表的頭(圖中標為_ChainHeader),可以通過(guò)m_begin計算得到:

_MemBlock* AutoFreeAlloc::_ChainHeader() const
{
    
return (_MemBlock*)(m_begin - HeaderSize);
}

為了使得_ChainHeader初始值為null,構造函數我們這樣寫(xiě):

AutoFreeAlloc::AutoFreeAlloc()
{
    m_begin 
= m_end = (char*)HeaderSize;
}

         下面我們考慮內存分配過(guò)程。Alloc過(guò)程主要會(huì )有三種情況,具體代碼為:

void* AutoFreeAlloc::Alloc(size_t cb)
{
    
if (m_end – m_begin < cb)
    {
        
if (cb >= BlockSize)
        {
                _MemBlock
* pHeader = _ChainHeader();
                _MemBlock
* pNew = (_MemBlock*)m_alloc.allocate(HeaderSize + cb);
                
if (pHeader)
                {
                 pNew
->pPrev = pHeader->pPrev;
                  pHeader
->pPrev = pNew;
                }
                
else
                {
                  m_end 
= m_begin = pNew->buffer;
                  pNew
->pPrev = NULL;
                }
                
return pNew->buffer;        }
        else
        {
            _MemBlock
* pNew = (_MemBlock*)malloc(sizeof(_MemBlock));
            pNew
->pPrev = _ChainHeader();
            m_begin 
= pNew->buffer;
            m_end 
= m_begin + BlockSize;
        }
    }
    
return m_end -= cb;
}

1.         最簡(jiǎn)單的情況,是當前_MemBlock還有足夠的自由內存(free memory),即:
    m_end – m_begin >= cb
此時(shí),只需要將m_end前移cb字節就可以了。我們畫(huà)個(gè)示意圖如下:

圖 3 

2.         在當前的_MemBlock的自由內存(free memory)不足的情況下,我們就需要申請一個(gè)新的_MemBlock以供使用[5]。申請新的_MemBlock,我們又會(huì )遇到兩種情況:

a)         申請的字節數(即cb)小于一個(gè)_MemBlock所能夠提供的內存(即BlockSize)。
這種情況下,我們只需要將該_MemBlock作為新的當前_MemBlock掛到鏈表中,剩下的工作就和情形1完全類(lèi)似。示意圖如下:

圖 4

b)        而在內存申請的字節數(即cb)大于或等于一個(gè)Block的字節數時(shí),我們需要申請可使用內存超過(guò)正常長(cháng)度(BlockSize)的_MemBlock。這個(gè)新生成的_MemBlock全部?jì)却姹挥脩?hù)申請。故此,我們只需要修改_ChainHeader的pPrev指針,改為指向這一塊新申請的_MemBlock即可。m_begin、m_end保持不變(當前的_MemBlock還是當前的_MemBlock)。如圖:

圖 5

         下面我們考慮內存釋放(Clear)過(guò)程。這個(gè)過(guò)程就是遍歷_MemBlock釋放所有的_MemBlock的過(guò)程,非常簡(jiǎn)單。代碼如下:

void AutoFreeAlloc::Clear()
{
    _MemBlock
* pHeader = _ChainHeader();
    
while (pHeader)
    {
        _MemBlock
* pTemp = pHeader->pPrev;
        free(pHeader);
        pHeader 
= pTemp;
    }
    m_begin 
= m_end = (char*)HeaderSize;
}

自動(dòng)析構過(guò)程

我們知道,C++以及其他面向對象語(yǔ)言為對象引入了構造、析構過(guò)程。這是一個(gè)了不起的發(fā)明。因為只有這樣,才能夠保證對象從一開(kāi)始產(chǎn)生以來(lái)(剛new出來(lái)),到對象銷(xiāo)毀這整個(gè)過(guò)程,它的數據都處于完備狀態(tài),是自洽的。

我們知道,C++以及其他面向對象語(yǔ)言為對象引入了構造、析構過(guò)程。這是一個(gè)了不起的發(fā)明。因為只有這樣,才能夠保證對象從一開(kāi)始產(chǎn)生以來(lái)(剛new出來(lái)),到對象銷(xiāo)毀這整個(gè)過(guò)程,它的數據都處于完備狀態(tài),是自洽的。

由于垃圾回收器負責對象的回收,它自然不止需要關(guān)注對象申請的內存的釋放,同時(shí)也需要保證,在對象銷(xiāo)毀之前它的析構過(guò)程被調用。上文我們?yōu)榱岁P(guān)注內存管理過(guò)程,把自動(dòng)析構過(guò)程需要的代碼均去除了。為了支持自動(dòng)析構,AutoFreeAlloc類(lèi)增加了以下成員:

class AutoFreeAlloc
{
    
struct _DestroyNode
{
        _DestroyNode
* pPrev;
        FnDestructor fnDestroy;
    };
    _DestroyNode
* m_destroyChain;
};

如果一個(gè)類(lèi)存在析構,則它需要在A(yíng)lloc內存的同時(shí)指定析構函數。代碼如下:

void* AutoFreeAlloc::Alloc(size_t cb, FnDestructor fn)
{
    _DestroyNode
* pNode = (_DestroyNode*)Alloc(sizeof(_DestroyNode) + cb);
    pNode
->fnDestroy = fn;
    pNode
->pPrev = m_destroyChain;
    m_destroyChain 
= pNode;
    
return pNode + 1;
}

只要通過(guò)該Alloc函數申請的內存,我們在Clear中就可以調用相應的析構。當然,Clear函數需要補充自動(dòng)析構相關(guān)的代碼:

void AutoFreeAlloc::Clear()
{
    
while (m_destroyChain)
    {
        m_destroyChain
->fnDestroy(m_destroyChain + 1);
        m_destroyChain 
= m_destroyChain->pPrev;
    }
    
// 以下是原先正常的內存釋放過(guò)程…
}

 

時(shí)間性能分析

void* AutoFreeAlloc::Alloc(size_t cb);

OOP技術(shù)帶來(lái)一個(gè)內存上的問(wèn)題是,對象粒度越來(lái)越細了,對象基本上都是小對象。這就對內存管理的性能提出了很高的要求?!?/p>

如果我們以對象大小平均為32字節計算的話(huà),每2048/32 = 64操作中,只有一次操作滿(mǎn)足m_end – m_begin < cb的條件。也就是說(shuō),在通常情況(63/64 = 98.4%的概率)下,Alloc操作只需要一個(gè)減法操作就完成內存分配。

我說(shuō)這是世界上最快速的內存分配算法,也許你對此仍然抱有懷疑態(tài)度。但是可以肯定的一點(diǎn)是,要突破它的性能極限我覺(jué)得已經(jīng)很難很難了?!?/p>

void AutoFreeAlloc::Clear();

一般內存管理器通常一次內存分配操作就需調用相應的一次Free操作。但是AutoFreeAlloc不針對每一個(gè)Alloc進(jìn)行釋放,而是針對每一個(gè)_MemBlock。仍假設對象平均大小為32字節的話(huà),也就是相當于把64次Alloc操作合并,為其提供一次相應的Free過(guò)程。

         結論:AutoFreeAlloc在時(shí)間上的性能,大約比普通的malloc/free的快64倍。

 

空間性能分析

我們知道,一般內存管理器為了將用戶(hù)申請的內存塊管理起來(lái),除了用戶(hù)需要的cb字節內存外,通常額外還提供一個(gè)內存塊的頭結構,通過(guò)這個(gè)頭結構將內存串連成為一個(gè)鏈表。一般來(lái)講,這個(gè)頭結構至少有兩項(可能還不止),示意如下:

struct MemHeader
{
    MemHeader
* pPrev;
    size_t cb;
};

仍然假設平均Alloc一次的內存為32字節。則一次malloc分配過(guò)程,就會(huì )浪費8/32 = 25%的內存。并且由于大量的小對象存在,整個(gè)內存中的碎片(指那些自由但無(wú)法被使用的內存)將特別嚴重。

而AutoFreeAlloc的Alloc沒(méi)有如何額外開(kāi)銷(xiāo)。整個(gè)AutoFreeAlloc,只有在將_MemBlock串為鏈表的有一個(gè)額外的pPrev指針,加上_MemBlock是malloc出來(lái)的,有額外的8字節開(kāi)銷(xiāo)??傆嬂速M(4+8)/2048 =0.6%的內存,幾乎可以忽略不計?!?/p>

后記

AutoFreeAlloc于2004-5-21開(kāi)發(fā),只有100行的代碼量。但是,這個(gè)組件獲得了空前的成功,它的應用范圍逐步擴大,超過(guò)了我最初實(shí)現這個(gè)組件時(shí)的預計。

我漸漸冷靜下來(lái),考慮這其中蘊涵的道理。我逐步領(lǐng)會(huì )到了,它的成功之處,不是它在時(shí)間、空間性能的高效,而是在于它幫助C++程序員解決了最大的難題——內存管理。雖然,這個(gè)解決方案并不是完整的。

AutoFreeAlloc是一個(gè)切入點(diǎn),從它身上,讓我明白了C++的new/delete的不合理;STL引入的allocator是一個(gè)切入點(diǎn),從它身上,讓我明白了內存管理有很強的區域性,在不同的區域(局部過(guò)程)中對allocator的需求卻又不盡相同。

我們前文也提到了一個(gè)例子:一個(gè)文檔打開(kāi),編輯,直到文檔被最終關(guān)閉,這個(gè)完成算不算局部過(guò)程呢?在A(yíng)utoFreeAlloc解決的問(wèn)題域來(lái)看,顯然我們無(wú)法認為它是一個(gè)局部過(guò)程。但是,從其他allocator角度來(lái)講,是否就有可能把它作為一個(gè)局部過(guò)程了呢?

正是考慮到AutoFreeAlloc的缺陷,我們需要一個(gè)功能更強的垃圾回收器。這就是我們下一次需要討論的組件了。

最后,仍然需要明確的一點(diǎn)時(shí)。我們很難也不需要實(shí)現一個(gè)象Java、C#那樣的垃圾回收器。提供一個(gè)具備特定的內存管理能力的allocator才是正道。




[1] 請參考boost官方網(wǎng)站http://www.boost.org/。

[2] mempool技術(shù)是一個(gè)很成熟的內存管理技術(shù),被sgi-stl、boost等C++庫實(shí)現者采用。

[3] 真正是否要把一個(gè)過(guò)程定義為局部過(guò)程,完全取決于設計者本身。例如,一個(gè)文檔打開(kāi),編輯,直到文檔被最終關(guān)閉,這個(gè)完成算不算局部過(guò)程呢?在大部分情況下我們認為它不是一個(gè)局部過(guò)程,但是下回我們將專(zhuān)門(mén)討論是否有可能,以及應該如何將它作為一個(gè)局部過(guò)程。

[4] 那些提供了垃圾回收器的語(yǔ)言的使用者,顯然也有應用了垃圾回收器的煩惱。例如C#在調用非管制代碼(如調用Win32 api)時(shí),這些問(wèn)題變得突出,一個(gè)疏忽就留下潛在隱患。這與C/C++程序員遺憾語(yǔ)言沒(méi)有垃圾回收器的感覺(jué)類(lèi)似。

[5] 當前的_MemBlock的自由內存很可能還是有的,但是不足cb字節。此時(shí)我們說(shuō)這里有內存碎片(memory piece):這些碎片盡管沒(méi)有人使用,但是我們把它棄而不用。

 

 

附加說(shuō)明:

本文所描述的AutoFreeAlloc組件,完整代碼可在WINX庫中找到。你也可以通過(guò)以下鏈接在線(xiàn)瀏覽:

AutoFreeAlloc完整源代碼

另外, 這篇文章寫(xiě)的時(shí)間較早,其規格雖然與現在的AutoFreeAlloc一樣,但成員函數名改了:

    Alloc -> allocate
    Clear -> clear

之所以這樣,是因為AutoFreeAlloc被納入stdext庫(這個(gè)庫可獨立于winx界面庫,是winx界面庫的基礎)。stdext庫的命名風(fēng)格盡量與STL的命名習慣一致。

相關(guān)文章:《C++內存管理變革


C++內存管理變革(3):另類(lèi)內存管理

最簡(jiǎn)單的C++/Java程序

最簡(jiǎn)單的Java程序:

class Program
{
   
public static void main()
   {
       
new int;
   }
}

對應的C++程序:

void main()
{
   
new int;
}

我想沒(méi)有一個(gè)Java程序員會(huì )認為上面的Java代碼存在問(wèn)題。但是所有嚴謹的C++程序員則馬上指出:上面這個(gè)C++程序有問(wèn)題,它存在內存泄漏。但是我今天想和大家交流的一個(gè)觀(guān)念是:這個(gè)C++程序沒(méi)有什么問(wèn)題。

DocX程序的內存管理

DocX是我開(kāi)發(fā)的一個(gè)文檔撰寫(xiě)工具。這里有關(guān)于它的一些介紹。在這一小節里,我要談?wù)勎以贒ocX中嘗試的另類(lèi)內存管理方法。 

DocX的總體流程是:

  1. 讀入一個(gè)C++源代碼(或頭)文件(.h/.c/.hpp/.cpp等),分析其中的注釋?zhuān)崛〔⑸蓌ml文檔。
  2. 通過(guò)xslt變換,將xml文檔轉換為htm。
  3. 分析源代碼中的所有include指令,取得相應的頭文件路徑,如果某個(gè)頭文件沒(méi)有分析過(guò),跳到1反復這些步驟。
  4. 最后所有生成的htm打包生成chm文件。

一開(kāi)始,我象Java/C#程序員做的那樣,我的代碼中所有的new均不考慮delete。當然,它一直運作得很好,直到有一天我的文檔累計到了一定程度后。正如我們預見(jiàn)的那樣,DocX程序運行崩潰了。

那么,怎么辦呢?找到所有需要delete的地方,補上delete?

這其實(shí)并不需要。在前面,我給大家介紹了AutoFreeAlloc(參見(jiàn)《C++內存管理變革(2):最袖珍的垃圾回收器》),也許有人在嘀咕,這樣一個(gè)內存分配器到底有何作用?!?wbr>—那么,現在你馬上可以看到它的典型用法之一了:

對于我們的DocX崩潰后,我只是做了以下改動(dòng):

  1. 加一個(gè)全局變量:std::AutoFreeAlloc alloc;
  2. 所有的new Type(arg1, arg2, …, argn),改為STD_NEW(alloc, Type)(arg1, arg2, …, argn);
  3. 所有的new Type[n],改為STD_NEW_ARRAY(alloc, Type, n);
  4. 每處理完一個(gè)源代碼文件時(shí),調用一次alloc.clear();

搞定,自此之后,DocX再也沒(méi)有內存泄漏,也不再有遇到內存不足而崩潰的情形。

只讀DOM模型(或允許少量修改)的建立

在《文本分析的三種典型設計模式》一文中我推薦大家使用DOM模型去進(jìn)行文件操作。并且通常情況下,這個(gè)DOM模型是只讀DOM模型(或允許少量修改)。 

對于只讀DOM模型,使用AutoFreeAlloc是極其方便的。整個(gè)DOM樹(shù)涉及的內存統一由同一個(gè)AutoFreeAlloc實(shí)例進(jìn)行分配。大體如下:

class Document;
class ObjectA
{
private:
    Document
* m_doc;
    SubObject
* m_c;
 
public:
    ObjectA(Document
* doc) : m_doc(doc) {
        m_c 
= STD_NEW(doc->alloc, SubObject);
    }
 
    SubObject
* getC() {
        
return m_c;
    }
};
 
class Document
{
public:
    AutoFreeAlloc alloc;
 
private:
    ObjectA
* m_a;
    ObjectB
* m_b;
 
public:
    ObjectA
* getA() {
        
if (m_a == NULL)
            m_a 
= STD_NEW(alloc, ObjectA)(this);
        
return m_a;
    }
};

通過(guò)這種方式創(chuàng )建的DOM模型,只要你刪除了Document對象,整個(gè)DOM樹(shù)自然就被刪除了。你根本不需要擔心其中有任何內存泄漏的可能。

另類(lèi)內存管理的觀(guān)念

通過(guò)以上內容,我試圖向大家闡述的一個(gè)觀(guān)點(diǎn)是:

  • 有了AutoFreeAlloc后,C++程序員也可以象GC語(yǔ)言的程序員一樣大膽n(yōu)ew而不需要顧忌什么時(shí)候delete。

展開(kāi)來(lái)講,可以有以下結論:

  • 如果你程序的空間復雜度為O(1),那么只new不delete是沒(méi)有問(wèn)題的。
  • 如果你程序的空間復雜度為O(n),并且是簡(jiǎn)單的n*O(1),那么可以用AutoFreeAlloc簡(jiǎn)化內存管理。
  • 如果你程序的空間復雜度為O(t),其中t是程序運行時(shí)間,并且你不能確定程序執行的總時(shí)間,那么AutoFreeAlloc并不直接適合你。比較典型的例子是Word、Excel等文檔編輯類(lèi)的程序。

用AutoFreeAlloc實(shí)現通用型的GC

AutoFreeAlloc對內存管理的環(huán)境進(jìn)行了簡(jiǎn)化,這種簡(jiǎn)化環(huán)境是常見(jiàn)的。在此環(huán)境下,C++程序員獲得了無(wú)可比擬的性能優(yōu)勢。當然,在一般情形下,AutoFreeAlloc并不適用。

那么,一個(gè)通用的半自動(dòng)GC環(huán)境在C++是否可能?《C++內存管理變革》系列的核心就是要告訴你:當然可以。并且,我們推薦C++程序員使用半自動(dòng)的GC,而不是Java/C# 中的那種GC。

通用的半自動(dòng)GC環(huán)境可以有很多種建立方式。這里我們簡(jiǎn)單聊一下如何使用AutoFreeAlloc去建立。

我們知道,使用AutoFreeAlloc,將導致程序隨著(zhù)時(shí)間推移,逐步地吃掉可用的內存。假設現在已經(jīng)到達我們設置的臨界點(diǎn),我們需要開(kāi)始gc。整個(gè)過(guò)程和Java等語(yǔ)言的gc其實(shí)完全類(lèi)似:通過(guò)一個(gè)根對象(Object* root),獲得所有活動(dòng)著(zhù)的對象(ActiveObjects),將它們復制到一個(gè)新的AutoFreeAlloc中:

Object* gc(AutoFreeAlloc& oldAlloc, Object* root, AutoFreeAlloc& newAlloc)
{
    Object
* root2 = root->clone(newAlloc);
    oldAlloc.clear();
    
return root2;
}

如果C++象Java/C#那樣有足夠豐富的元信息,那么Object::clone過(guò)程就可以象Java/C#等語(yǔ)言那樣自動(dòng)完成。這些元信息對于GC過(guò)程的用處無(wú)非在于,我們可以遍歷整個(gè)活動(dòng)對象的集合,然后把這些活動(dòng)對象復制一份。沒(méi)有復制過(guò)來(lái)的對象自然而然就被丟棄了。

GC的原理就是這么簡(jiǎn)單。沒(méi)有元信息也沒(méi)關(guān)系,只要我們要求每個(gè)由GC托管的對象支持clone函數,一切就ok了。對于一個(gè)復雜程序,要求每個(gè)對象提供clone函數不見(jiàn)得是什么過(guò)分的要求,clone函數也不只有g(shù)c過(guò)程才需要,很多對象在設計上天然就需要clone。

補充說(shuō)明

關(guān)于全局AutoFreeAlloc變量

我個(gè)人非常不推薦使用全局變量(除非是常量:不一定用const修飾,指的是經(jīng)過(guò)一定初始化步驟后就不在修改的變量)。上面只是對于小型的單線(xiàn)程程序偷懶才這樣做。

關(guān)于用AutoFreeAlloc實(shí)現通用型的GC

請注意我沒(méi)有討論過(guò)于細節的東西。如果你決定選擇這種做法,請仔細推敲細節??梢灶A見(jiàn)的一些細節有:

  • AutoFreeAlloc與線(xiàn)程模型(ThreadModel)。AutoFreeAlloc關(guān)注點(diǎn)在于快,它通常不涉及跨線(xiàn)程問(wèn)題。但是如果要作為通用型的GC,這一點(diǎn)不能不考慮。為了性能,推薦每個(gè)線(xiàn)程獨立管理內存,而不要使用互斥體。
  • 性能優(yōu)化??梢钥紤]象Java的GC那樣,使用兩個(gè)AutoFreeAlloc,把對象劃分為年輕代和年老代。


C++內存管理變革(4): boost::object_pool

許式偉 (版權聲明)
2007-4-21

這篇文章拖的有點(diǎn)久了。NeutralEvil 在3個(gè)月之前就在催促我繼續寫(xiě)了。只是出于WinxGui完整性的考慮,我一直在刻意優(yōu)先去補充其它方面的文章,而不是讓人去誤會(huì )WinxGui是一個(gè)內存管理庫了。:)

言歸正傳。我們在內存池(MemPool)技術(shù)詳解已經(jīng)介紹了boost::pool組件。從內存管理觀(guān)念的變革來(lái)看,這是是一個(gè)傳統的MemPool組件,盡管也有一定的改進(jìn)(但只是性能上的改進(jìn))。但boost::object_pool不同,它與我在C++內存管理變革強調的觀(guān)念非常吻合??梢哉J為,boost::object_pool是一種不通用的gc allocator組件。

我已經(jīng)多次提出gc allocator的概念。這里仍然需要強調一下,所謂gc allocator,是指具垃圾回收能力的allocator。C++內存管理變革(1) 中我們引入了這個(gè)概念,但是沒(méi)有明確gc allocator一詞。

boost::object_pool內存管理觀(guān)念

boost::object_pool的了不起之處在于,這是C++從庫的層次上頭一次承認,程序員在內存管理上是會(huì )犯錯誤的,由程序員來(lái)確保內存不泄漏是困難的。boost::object_pool允許你忘記釋放內存。我們來(lái)看一個(gè)例子:

    class X { … };
 
    
void func()
    {
        boost::object_pool
<X> alloc;
 

        X* obj1 = alloc.construct();
        X
* obj2 = alloc.construct();
        alloc.destroy(obj2);
    }

如果boost::object_pool只是一個(gè)普通的allocator,那么這段代碼顯然存在問(wèn)題,因為obj1的析構函數沒(méi)有執行,申請的內存也沒(méi)有釋放。

但是這段代碼是完全正常的。是的,obj1的析構確實(shí)執行了,所申請內存也被釋放了。這就是說(shuō),boost::object_pool既支持你手工釋放內存(通過(guò)主動(dòng)調用object_pool::destroy),也支持內存的自動(dòng)回收(通過(guò)object_pool::~object_pool析構的執行)。這正符合gc allocator的規格。

注:內存管理更好的說(shuō)法是對象管理。內存的申請和釋放更確切的說(shuō)是對象的創(chuàng )建和銷(xiāo)毀。但是這里我們不刻意區分這兩者的差異。

boost::object_pool與AutoFreeAlloc

我們知道,AutoFreeAlloc不支持手工釋放,而只能等到AutoFreeAlloc對象析構的時(shí)候一次性全部釋放內存。那么,是否可以認為boost::object_pool是否比AutoFreeAlloc更加完備呢?

其實(shí)不然。boost::object_pool與AutoFreeAlloc都不是完整意義上的gc allocator。AutoFreeAlloc因為它只能一次性釋放,故此僅僅適用特定的用況。然而盡管AutoFreeAlloc不是普適的,但它是通用型的gc allocator。而boost::object_pool只能管理一種對象,并不是通用型的allocator,局限性其實(shí)更強。

boost::object_pool的實(shí)現細節

大家對boost::object_pool應該已經(jīng)有了一個(gè)總體的把握?,F在,讓我們深入到object_pool的實(shí)現細節中去。

內存池(MemPool)技術(shù)詳解中,我們介紹boost::pool組件時(shí),特意提醒大家留意pool::ordered_malloc/ordered_free函數。事實(shí)上,boost::object_poolmalloc/construct, free/destroy函數調用了pool::ordered_malloc, ordered_free函數,而不是pool::malloc, free函數。

讓我們解釋下為什么。

其實(shí)這其中的關(guān)鍵,在于object_pool要支持手工釋放內存和自動(dòng)回收內存(并自動(dòng)執行析構函數)兩種模式。如果沒(méi)有自動(dòng)析構,那么普通的MemPool就足夠了,也就不需要ordered_free。既然有自動(dòng)回收,同時(shí)又存在手工釋放,那么就需要區分內存塊(MemBlock)中哪些結點(diǎn)(Node)是自由內存結點(diǎn)(FreeNode),哪些結點(diǎn)是已經(jīng)使用的。對于哪些已經(jīng)是自由內存的結點(diǎn),顯然不能再調用對象的析構函數。

我們來(lái)看看object_pool::~object_pool函數的實(shí)現:

template <typename T, typename UserAllocator>
object_pool
<T, UserAllocator>::~object_pool()
{
  
// handle trivial case
  if (!this->list.valid())
    
return;
 
  details::PODptr
<size_type> iter = this->list;
  details::PODptr
<size_type> next = iter;
 
  
// Start ’freed_iter’ at beginning of free list
  void * freed_iter = this->first;
 
  
const size_type partition_size = this->alloc_size();
 
  
do
  {
    
// increment next
    next = next.next();
  
    
// delete all contained objects that aren’t freed
  
    
// Iterate ’i‘ through all chunks in the memory block
    for (char * i = iter.begin(); i != iter.end(); i += partition_size)
    {
      
// If this chunk is free
      if (i == freed_iter)
      {
        
// Increment freed_iter to point to next in free list
        freed_iter = nextof(freed_iter);
 
        
// Continue searching chunks in the memory block
        continue;
      }
  
      
// This chunk is not free (allocated), so call its destructor
      static_cast<*>(static_cast<void *>(i))->~T();
      
// and continue searching chunks in the memory block
    }
  
    
// free storage
    UserAllocator::free(iter.begin());
  
    
// increment iter
    iter = next;
  } 
while (iter.valid());
  
  
// Make the block list empty so that the inherited destructor doesn’t try to
  
//  free it again.
  this->list.invalidate();
}

這段代碼不難理解,object_pool遍歷所有申請的內存塊(MemBlock),并遍歷其中所有結點(diǎn)(Node),如果該結點(diǎn)不出現在自由內存結點(diǎn)(FreeNode)的列表(FreeNodeList)中,那么,它就是用戶(hù)未主動(dòng)釋放的結點(diǎn),需要進(jìn)行相應的析構操作。

現在你明白了,ordered_malloc是為了讓MemBlockList中的MemBlock有序,ordered_free是為了讓FreeNodeList中的所有FreeNode有序。而MemBlockList,FreeNodeList有序,是為了更快地檢測Node是自由的還是被使用的(這實(shí)際上是一個(gè)集合求交的流程,建議你看看std::set_intersection,它定義在STL的<algorithm>中)。

C++內存管理變革-系列文章 

點(diǎn)擊這里查看更多內存管理相關(guān)文章。






本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
C++內存管理變革(4):boost::object
java內存管理
GC回收機制
談?wù)?NET中常見(jiàn)的內存泄露問(wèn)題——GC、委托事件和弱引用
C#技術(shù)漫談之垃圾回收機制(GC)
Java垃圾收集算法與內存泄露
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久