#
來(lái)源:http://www.tbdata.org/archives/1390#more-1390
Nginx的內(nèi)存池實(shí)現(xiàn)得很精巧,代碼也很簡(jiǎn)潔。總的來(lái)說(shuō),所有的內(nèi)存池基本都一個(gè)宗旨:申請(qǐng)大塊內(nèi)存,避免“細(xì)水長(zhǎng)流”。
一、創(chuàng)建一個(gè)內(nèi)存池
nginx內(nèi)存池主要有下面兩個(gè)結(jié)構(gòu)來(lái)維護(hù),他們分別維護(hù)了內(nèi)存池的頭部和數(shù)據(jù)部。此處數(shù)據(jù)部就是供用戶分配小塊內(nèi)存的地方。
//該結(jié)構(gòu)用來(lái)維護(hù)內(nèi)存池的數(shù)據(jù)塊,供用戶分配之用。
typedef struct {
u_char *last; //當(dāng)前內(nèi)存分配結(jié)束位置,即下一段可分配內(nèi)存的起始位置
u_char *end; //內(nèi)存池結(jié)束位置
ngx_pool_t *next; //鏈接到下一個(gè)內(nèi)存池
ngx_uint_t failed; //統(tǒng)計(jì)該內(nèi)存池不能滿足分配請(qǐng)求的次數(shù)
} ngx_pool_data_t;
//該結(jié)構(gòu)維護(hù)整個(gè)內(nèi)存池的頭部信息。
struct ngx_pool_s {
ngx_pool_data_t d; //數(shù)據(jù)塊
size_t max; //數(shù)據(jù)塊的大小,即小塊內(nèi)存的最大值
ngx_pool_t *current; //保存當(dāng)前內(nèi)存池
ngx_chain_t *chain; //可以掛一個(gè)chain結(jié)構(gòu)
ngx_pool_large_t *large; //分配大塊內(nèi)存用,即超過(guò)max的內(nèi)存請(qǐng)求
ngx_pool_cleanup_t *cleanup; //掛載一些內(nèi)存池釋放的時(shí)候,同時(shí)釋放的資源。
ngx_log_t *log;
};
有了上面的兩個(gè)結(jié)構(gòu),就可以創(chuàng)建一個(gè)內(nèi)存池了,nginx用來(lái)創(chuàng)建一個(gè)內(nèi)存池的接口是:ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)(位于src/core/ngx_palloc.c中);調(diào)用這個(gè)函數(shù)就可以創(chuàng)建一個(gè)大小為size的內(nèi)存池了。這里,我用內(nèi)存池的結(jié)構(gòu)圖來(lái)展 示,就不做具體的代碼分析了。

ngx_create_pool接口函數(shù)就是分配上圖這樣的一大塊內(nèi)存,然后初始化好各個(gè)頭部字段(上圖中的彩色部分)。紅色表示的四個(gè)字段就是來(lái)自于上 述的第一個(gè)結(jié)構(gòu),維護(hù)數(shù)據(jù)部分,由圖可知:last是用戶從內(nèi)存池分配新內(nèi)存的開(kāi)始位置,end是這塊內(nèi)存池的結(jié)束位置,所有分配的內(nèi)存都不能超過(guò) end。藍(lán)色表示的max字段的值等于整個(gè)數(shù)據(jù)部分的長(zhǎng)度,用戶請(qǐng)求的內(nèi)存大于max時(shí),就認(rèn)為用戶請(qǐng)求的是一個(gè)大內(nèi)存,此時(shí)需要在紫色表示的large 字段下面單獨(dú)分配;用戶請(qǐng)求的內(nèi)存不大于max的話,就是小內(nèi)存申請(qǐng),直接在數(shù)據(jù)部分分配,此時(shí)將會(huì)移動(dòng)last指針。
二、分配小塊內(nèi)存(size <= max)
上面創(chuàng)建好了一個(gè)可用的內(nèi)存池了,也提到了小塊內(nèi)存的分配問(wèn)題。nginx提供給用戶使用的內(nèi)存分配接口有:
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
ngx_palloc和ngx_pnalloc都是從內(nèi)存池里分配size大小內(nèi)存,至于分得的是小塊內(nèi)存還是大塊內(nèi)存,將取決于size的大小; 他們的不同之處在于,palloc取得的內(nèi)存是對(duì)齊的,pnalloc則否。ngx_pcalloc是直接調(diào)用palloc分配好內(nèi)存,然后進(jìn)行一次0初 始化操作。ngx_pmemalign將在分配size大小的內(nèi)存并按alignment對(duì)齊,然后掛到large字段下,當(dāng)做大塊內(nèi)存處理。下面用圖形 展示一下分配小塊內(nèi)存的模型:

上圖這個(gè)內(nèi)存池模型是由上3個(gè)小內(nèi)存池構(gòu)成的,由于第一個(gè)內(nèi)存池上剩余的內(nèi)存不夠分配了,于是就創(chuàng)建了第二個(gè)新的內(nèi)存池,第三個(gè)內(nèi)存池是由于前面兩個(gè)內(nèi)存 池的剩余部分都不夠分配,所以創(chuàng)建了第三個(gè)內(nèi)存池來(lái)滿足用戶的需求。由圖可見(jiàn):所有的小內(nèi)存池是由一個(gè)單向鏈表維護(hù)在一起的。這里還有兩個(gè)字段需要關(guān) 注,failed和current字段。failed表示的是當(dāng)前這個(gè)內(nèi)存池的剩余可用內(nèi)存不能滿足用戶分配請(qǐng)求的次數(shù),即是說(shuō):一個(gè)分配請(qǐng)求到來(lái)后,在 這個(gè)內(nèi)存池上分配不到想要的內(nèi)存,那么就failed就會(huì)增加1;這個(gè)分配請(qǐng)求將會(huì)遞交給下一個(gè)內(nèi)存池去處理,如果下一個(gè)內(nèi)存池也不能滿足,那么它的 failed也會(huì)加1,然后將請(qǐng)求繼續(xù)往下傳遞,直到滿足請(qǐng)求為止(如果沒(méi)有現(xiàn)成的內(nèi)存池來(lái)滿足,會(huì)再創(chuàng)建一個(gè)新的內(nèi)存池)。current字段會(huì)隨著 failed的增加而發(fā)生改變,如果current指向的內(nèi)存池的failed達(dá)到了4的話,current就指向下一個(gè)內(nèi)存池了。猜測(cè):4這個(gè)值應(yīng)該是 作者的經(jīng)驗(yàn)值,或者是一個(gè)統(tǒng)計(jì)值。
三、大塊內(nèi)存的分配(size > max)
大塊內(nèi)存的分配請(qǐng)求不會(huì)直接在內(nèi)存池上分配內(nèi)存來(lái)滿足,而是直接向操作系統(tǒng)申請(qǐng)這么一塊內(nèi)存(就像直接使用malloc分配內(nèi)存一樣),然后將這塊 內(nèi)存掛到內(nèi)存池頭部的large字段下。內(nèi)存池的作用在于解決小塊內(nèi)存池的頻繁申請(qǐng)問(wèn)題,對(duì)于這種大塊內(nèi)存,是可以忍受直接申請(qǐng)的。同樣,用圖形展示大塊 內(nèi)存申請(qǐng)模型:

注意每塊大內(nèi)存都對(duì)應(yīng)有一個(gè)頭部結(jié)構(gòu)(next&alloc),這個(gè)頭部結(jié)構(gòu)是用來(lái)將所有大內(nèi)存串成一個(gè)鏈表用的。這個(gè)頭部結(jié)構(gòu)不是直接向操作系 統(tǒng)申請(qǐng)的,而是當(dāng)做小塊內(nèi)存(頭部結(jié)構(gòu)沒(méi)幾個(gè)字節(jié))直接在內(nèi)存池里申請(qǐng)的。這樣的大塊內(nèi)存在使用完后,可能需要第一時(shí)間釋放,節(jié)省內(nèi)存空間,因此 nginx提供了接口函數(shù):ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);此函數(shù)專門用來(lái)釋放某個(gè)內(nèi)存池上的某個(gè)大塊內(nèi)存,p就是大內(nèi)存的地址。ngx_pfree只會(huì)釋放大內(nèi)存,不會(huì)釋放其對(duì)應(yīng)的頭部結(jié)構(gòu),畢竟頭部結(jié) 構(gòu)是當(dāng)做小內(nèi)存在內(nèi)存池里申請(qǐng)的;遺留下來(lái)的頭部結(jié)構(gòu)會(huì)作下一次申請(qǐng)大內(nèi)存之用。
四、cleanup資源

可以看到所有掛載在內(nèi)存池上的資源將形成一個(gè)循環(huán)鏈表,一路走來(lái),發(fā)現(xiàn)鏈表這種看似簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)卻被頻繁使用。由圖可知,每個(gè)需要清理的資源都對(duì)應(yīng)有一 個(gè)頭部結(jié)構(gòu),這個(gè)結(jié)構(gòu)中有一個(gè)關(guān)鍵的字段handler,handler是一個(gè)函數(shù)指針,在掛載一個(gè)資源到內(nèi)存池上的時(shí)候,同時(shí)也會(huì)注冊(cè)一個(gè)清理資源的函 數(shù)到這個(gè)handler上。即是說(shuō),內(nèi)存池在清理cleanup的時(shí)候,就是調(diào)用這個(gè)handler來(lái)清理對(duì)應(yīng)的資源。比如:我們可以將一個(gè)開(kāi)打的文件描 述符作為資源掛載到內(nèi)存池上,同時(shí)提供一個(gè)關(guān)閉文件描述的函數(shù)注冊(cè)到handler上,那么內(nèi)存池在釋放的時(shí)候,就會(huì)調(diào)用我們提供的關(guān)閉文件函數(shù)來(lái)處理文 件描述符資源了。
五、內(nèi)存的釋放
nginx只提供給了用戶申請(qǐng)內(nèi)存的接口,卻沒(méi)有釋放內(nèi)存的接口,那么nginx是如何完成內(nèi)存釋放的呢?總不能一直申請(qǐng),用不釋放啊。針對(duì)這個(gè)問(wèn) 題,nginx利用了web server應(yīng)用的特殊場(chǎng)景來(lái)完成;一個(gè)web server總是不停的接受connection和request,所以nginx就將內(nèi)存池分了不同的等級(jí),有進(jìn)程級(jí)的內(nèi)存池、connection級(jí) 的內(nèi)存池、request級(jí)的內(nèi)存池。也就是說(shuō),創(chuàng)建好一個(gè)worker進(jìn)程的時(shí)候,同時(shí)為這個(gè)worker進(jìn)程創(chuàng)建一個(gè)內(nèi)存池,待有新的連接到來(lái)后,就 在worker進(jìn)程的內(nèi)存池上為該連接創(chuàng)建起一個(gè)內(nèi)存池;連接上到來(lái)一個(gè)request后,又在連接的內(nèi)存池上為request創(chuàng)建起一個(gè)內(nèi)存池。這樣, 在request被處理完后,就會(huì)釋放request的整個(gè)內(nèi)存池,連接斷開(kāi)后,就會(huì)釋放連接的內(nèi)存池。因而,就保證了內(nèi)存有分配也有釋放。
總結(jié):通過(guò)內(nèi)存的分配和釋放可以看出,nginx只是將小塊內(nèi)存的申請(qǐng)聚集到一起申請(qǐng),然后一起釋放。避免了頻繁申請(qǐng)小內(nèi)存,降低內(nèi)存碎片的產(chǎn)生等問(wèn)題
參考:STL源碼分析
(一)vector容器
vector的數(shù)據(jù)安排以及操作方式,與array非常相似。兩者的唯一區(qū)別在于空間的運(yùn)用的靈活性。array是靜態(tài)空間,一旦配置了就不能改變。vector是動(dòng)態(tài)空間,隨著元素的加入,它的內(nèi)部機(jī)制會(huì)自行擴(kuò)充空間以容納新元素。因此,vector的運(yùn)用對(duì)于內(nèi)存的合理利用與運(yùn)用的靈活性有很大的幫助,我們?cè)僖膊槐匾驗(yàn)楹ε驴臻g不足而一開(kāi)始要求一個(gè)大塊的array。
vector動(dòng)態(tài)增加大小,并不是在原空間之后持續(xù)新空間(因?yàn)闊o(wú)法保證原空間之后尚有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然后將原內(nèi)容拷貝過(guò)來(lái),然后才開(kāi)始在原內(nèi)容之后構(gòu)造新元素,并釋放原空間。因此,對(duì)vector的任何操作,一旦引起空間重新配置,指向原vector的所有迭代器就都失效了。
(二)list容器
相對(duì)于vector的連續(xù)空間,list就顯得復(fù)雜許多,它的好處是每次插入或刪除一個(gè)元素,就配置或釋放一個(gè)元素空間。因此,list對(duì)于空間的運(yùn)用有絕對(duì)的精準(zhǔn),一點(diǎn)也不浪費(fèi)。而且,對(duì)于任何位置的元素插入或元素移除,list永遠(yuǎn)是常數(shù)時(shí)間。STL中的list是一個(gè)雙向鏈表,而且是一個(gè)環(huán)狀雙向鏈表。
(三)deque容器
deque 是一種雙向開(kāi)口的連續(xù)線性空間。所謂雙向開(kāi)口,意思是可以在隊(duì)尾兩端分別做元素的插入和刪除操作。deque和vector的最大差異,一在于deque允許于常數(shù)時(shí)間內(nèi)對(duì)起頭端進(jìn)行元素的插入或移除操作,二在于deque沒(méi)有所謂容量觀念,因?yàn)樗莿?dòng)態(tài)地以分段連續(xù)空間組合而成,隨時(shí)可以增加一段新的空間并鏈接在一起。換句話說(shuō),像vector那樣"因舊空間不足而重新配置一塊更大空間,然后復(fù)制元素,再釋放舊空間"這樣的事情在 deque是不會(huì)發(fā)生的。
deque是由一段一段的定量連續(xù)空間構(gòu)成。一旦有必要在deque的前端或尾端增加新空間,便配置一段定量連續(xù)空間,串接在整個(gè)deque的頭端或尾端。deque的最大任務(wù),便是在這些分段的定量連續(xù)空間上,維護(hù)其整體連續(xù)的假象,并提供隨機(jī)存取的接口。避開(kāi)了"重新配置,復(fù)制,釋放"的輪回,代價(jià)則是復(fù)雜的迭代器架構(gòu)。因?yàn)橛蟹侄芜B續(xù)線性空間,就必須有中央控制,而為了維持整體連續(xù)的假象,數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)及迭代器前進(jìn)后退等操作都頗為繁瑣。
deque采用一塊所謂的map作為主控。這里的map是一小塊連續(xù)空間,其中每個(gè)元素都是指針,指向另一段連續(xù)線性空間,稱為緩沖區(qū)。緩沖區(qū)才是deque的存儲(chǔ)空間主體。SGI STL允許我們指定緩沖區(qū)大小,默認(rèn)值0表示將使用512 bytes緩沖區(qū)。
(四)stack
stack 是一種先進(jìn)后出(First In Last Out , FILO)的數(shù)據(jù)結(jié)構(gòu)。它只有一個(gè)出口,stack 允許新增元素,移除元素,取得最頂端元素。但除了最頂端外,沒(méi)有任何其它方法可以存取stack的其它元素,stack不允許遍歷行為。
以某種容器作為底部結(jié)構(gòu),將其接口改變,使之符合“先進(jìn)后出”的特性,形成一個(gè)stack,是很容易做到的。deque是雙向開(kāi)口的數(shù)據(jù)結(jié)構(gòu),若以deque為底部結(jié)構(gòu)并封閉其頭端開(kāi)口,便輕而易舉地形成了一個(gè)stack.因此,SGI STL 便以deque作為缺省情況下的stack底部結(jié)構(gòu),由于stack 系以底部容器完成其所有工作,而具有這種"修改某物接口,形成另一種風(fēng)貌"之性質(zhì)者,稱為adapter(配接器),因此,STL stack 往往不被歸類為container(容器),而被歸類為 container adapter.
(五) queue
queue是一種先進(jìn)先出(First In First Out,FIFO) 的數(shù)據(jù)結(jié)構(gòu)。它有兩個(gè)出口,queue允許新增元素,移除元素,從最底端加入元素,取得最頂端元素。但除了最底端可以加入,最頂端可以取出外,沒(méi)有任何其它方法可以存取queue的其它元素。
以某種容器作為底部結(jié)構(gòu),將其接口改變,使之符合“先進(jìn)先出”的特性,形成一個(gè)queue,是很容易做到的。deque是雙向開(kāi)口的數(shù)據(jù)結(jié)構(gòu),若以 deque為底部結(jié)構(gòu)并封閉其底部的出口和前端的入口,便輕而易舉地形成了一個(gè)queue.因此,SGI STL 便以deque作為缺省情況下的queue底部結(jié)構(gòu),由于queue 系以底部容器完成其所有工作,而具有這種"修改某物接口,形成另一種風(fēng)貌"之性質(zhì)者,稱為adapter(配接器),因此,STL queue往往不被歸類為container(容器),而被歸類為 container adapter.
(六)heap
heap并不歸屬于STL容器組件,它是個(gè)幕后英雄,扮演priority queue的助手。priority queue允許用戶以任何次序?qū)⑷魏卧赝迫肴萜髦校〕鰰r(shí)一定按從優(yōu)先權(quán)最高的元素開(kāi)始取。按照元素的排列方式,heap可分為max-heap和min-heap兩種,前者每個(gè)節(jié)點(diǎn)的鍵值(key)都大于或等于其子節(jié)點(diǎn)鍵值,后者的每個(gè)節(jié)點(diǎn)鍵值(key)都小于或等于其子節(jié)點(diǎn)鍵值。因此, max-heap的最大值在根節(jié)點(diǎn),并總是位于底層array或vector的起頭處;min-heap的最小值在根節(jié)點(diǎn),亦總是位于底層array或vector起頭處。STL 供應(yīng)的是max-heap,用c++實(shí)現(xiàn)。
堆排序c語(yǔ)言實(shí)現(xiàn)
http://www.shnenglu.com/tankzhouqiang/archive/2011/03/21/142413.html
(七)priority_queue
priority_queue是一個(gè)擁有權(quán)值觀念的queue,它允許加入新元素,移除舊元素,審視元素值等功能。由于這是一個(gè)queue,所以只允許在底端加入元素,并從頂端取出元素,除此之外別無(wú)其它存取元素的途徑。priority_queue帶有權(quán)值觀念,其內(nèi)的元素并非依照被推入的次序排列,而是自動(dòng)依照元素的權(quán)值排列(通常權(quán)值以實(shí)值表示)。權(quán)值最高者,排在最前面。缺省情況下priority_queue系利用一個(gè)max-heap完成,后者是一個(gè)以vector表現(xiàn)的 complete binary tree.max-heap可以滿足priority_queue所需要的"依權(quán)值高低自動(dòng)遞減排序"的特性。
priority_queue完全以底部容器作為根據(jù),再加上heap處理規(guī)則,所以其實(shí)現(xiàn)非常簡(jiǎn)單。缺省情況下是以vector為底部容器。queue以底部容器完成其所有工作。具有這種"修改某物接口,形成另一種風(fēng)貌"之性質(zhì)者,稱為adapter(配接器),因此,STL priority_queue往往不被歸類為container(容器),而被歸類為container adapter.
(八)set,multiset
set的特性是,所有元素都會(huì)根據(jù)元素的鍵值自動(dòng)被排序。set的元素不像map那樣可以同時(shí)擁有實(shí)值(value)和鍵值(key), set 元素的鍵值就是實(shí)值,實(shí)值就是鍵值,set不允許兩個(gè)元素有相同的值。set是通過(guò)紅黑樹(shù)來(lái)實(shí)現(xiàn)的,由于紅黑樹(shù)(RB-tree)是一種平衡二叉搜索樹(shù),自動(dòng)排序的效果很不錯(cuò),所以標(biāo)準(zhǔn)的STL的set即以RB-Tree為底層機(jī)制。又由于set所開(kāi)放的各種操作接口,RB-tree也都提供了,所以幾乎所有的set操作行為,都只有轉(zhuǎn)調(diào)用RB-tree的操作行為而已。
multiset的特性以及用法和set完全相同,唯一的差別在于它允許鍵值重復(fù),因此它的插入操作采用的是底層機(jī)制RB-tree的insert_equal()而非insert_unique().
(九)map,multimap
map的特性是,所有元素都會(huì)根據(jù)元素的鍵值自動(dòng)被排序。map的所有元素都是pair,同時(shí)擁有實(shí)值(value)和鍵值(key). pair的第一元素被視為鍵值,第二元素被視為實(shí)值。map不允許兩個(gè)元素?fù)碛邢嗤逆I值.由于RB-tree是一種平衡二叉搜索樹(shù),自動(dòng)排序的效果很不錯(cuò),所以標(biāo)準(zhǔn)的STL map即以RB-tree為底層機(jī)制。又由于map所開(kāi)放的各種操作接口,RB-tree也都提供了,所以幾乎所有的map操作行為,都只是轉(zhuǎn)調(diào)RB-tree的操作行為。
multimap的特性以及用法與map完全相同,唯一的差別在于它允許鍵值重復(fù),因此它的插入操作采用的是底層機(jī)制RB-tree的insert_equal()而非insert_unique。
(一)迪杰斯特拉算法(時(shí)間復(fù)雜度O(n2))
迪杰斯特拉(Dijkstra)算法是求某個(gè)源點(diǎn)到其余各頂點(diǎn)的最短路徑,這是一個(gè)按路徑長(zhǎng)度遞增的次序產(chǎn)生最短路徑的算法。
首先引進(jìn)一個(gè)輔助向量D,它的每個(gè)分量D[i]表示當(dāng)前所找到的從始點(diǎn)v到每個(gè)終點(diǎn)vi的最短路徑的長(zhǎng)度。它的初態(tài)為:若從v到vi有弧,則D[i]為弧上的權(quán)值;否則置D[i]為無(wú)窮大。顯然,長(zhǎng)度為D[j]=Min{D[i]|vi屬于V}的路徑就是從v出發(fā)的長(zhǎng)度最短的一條最短路徑。因此,在一般情況下,下一條長(zhǎng)度次短的最短路徑的長(zhǎng)度必為D[j]=Min{D[i]|vi 屬于 V-S} 其中D[i]或者為弧(v,vi)上的權(quán)值,或者是D[k](vk屬于S)和弧(vk,vi)上的權(quán)值之和。算法步驟如下:
(1)假設(shè)用帶權(quán)的鄰接矩陣arcs來(lái)表示帶權(quán)有向圖,arcs[i][j]表示弧(vi,vj)上的權(quán)值。若(vi,vj)不存在,則置arcs[i][j]為無(wú)窮大。S為已找到從v出發(fā)的最短路徑的終點(diǎn)的集合,它的初始狀態(tài)為空集。那么,從v出發(fā)到圖上其余各頂點(diǎn)(終點(diǎn))vi,可能達(dá)到的最短路徑長(zhǎng)度的初值為:
D[i]=G.arcs[v][vi],vi屬于V
(2)選擇Vj,使得
D[j]=Min{D[i]|vi 屬于V-S}
vj 就是當(dāng)前求得的一條從v出發(fā)的最短路徑的終點(diǎn)。令
S=SU {j}
(3) 修改從v出發(fā)到集合V-S上任一頂點(diǎn)vk可達(dá)的最短路徑長(zhǎng)度。如果D[j]+arcs[j][k]<D[k]則修改D[k]為 D[k]=D[j]+arcs[j][k]
(4) 重復(fù)操作(2),(3)共 n-1次。由此求得從v到圖上其余各頂點(diǎn)的最短路徑是依路徑長(zhǎng)度遞增的序列。
(二)弗洛伊德(Floyd)算法(時(shí)間復(fù)雜度為O(n3))
弗洛伊德(Floyd)算法是求圖中每一對(duì)頂點(diǎn)之間的最短路徑,時(shí)間復(fù)雜度為O(n3).
弗洛伊德算法仍從圖的帶權(quán)鄰接矩陣cost出發(fā),其基本思想是 :
假設(shè)求從頂點(diǎn)vi到vj的最短路徑。如果從vi到vj有弧,則從vi到vj存在一條長(zhǎng)度arcs[i][j]的路徑,該路徑不一定是最短路徑,尚需進(jìn)行n次試探。首先考慮路徑(vi,v0,vj)是否存在(即判別弧(vi,v0)和(v0,vj)是否存在)。 如果存在,則比較(vi,vj)和(vi,v0,vj)是否存在(即判別弧(vi,v0)和(v0,vj)是否存在).如果存在,則比較(vi,vj)和(vi,v0,vj)的路徑長(zhǎng)度取長(zhǎng)度較短者為從vi到vj的中間頂點(diǎn)的序號(hào)不大于0的最短路徑。假如在路徑上再增加一個(gè)頂點(diǎn)v1,也就是說(shuō),如果(vi,...v1)和(v1...vj)分別為當(dāng)前找到的中間頂點(diǎn)的序號(hào)不大于0的最短路徑,那么(vi,...,v1,...vj)就有可能是從vi到vj的中間頂點(diǎn)的序號(hào)不大于1的最短路徑。將它和已經(jīng)得到的從vi到vj中間的頂點(diǎn)序號(hào)不大于0的最短路徑相比較,從中選出中間頂點(diǎn)的序號(hào)不大于1的最短路徑之后,再增加一個(gè)頂點(diǎn)v2,繼續(xù)進(jìn)行試探。依次類推。在一般情況下,若(vi,...,vk)和(vk,...vj)分別是從vi到vk和從vk到vj的中間頂點(diǎn)的序號(hào)不大于k-1的最短路徑,則將(vi,...vk,...vj)和已經(jīng)得到的從vi到vj且中間頂點(diǎn)序號(hào)不大于k-1的最短路徑相比較,其長(zhǎng)度較短者便是從vi到vj的中間頂點(diǎn)的序號(hào)不大于k的最短路徑。這樣,在經(jīng)過(guò)n次比較后,最后求得的必是從vi到vj的最短路徑。
現(xiàn)定義一個(gè)n階方陣序列
D(-1),D(0),D(1),...D(k),...D(n-1)
其中
D(-1)[i][j]=G.arcs[i][j].
D(k)[i][j]=Min{D(k-1)[i][j],D(k-1)[i][k]+D(k-1)[k][j]} 0<=k<=n-1
從上述計(jì)算公式可見(jiàn),D(1)[i][j]是從vi到vj的中間頂點(diǎn)的序號(hào)不大于1的最短路徑的長(zhǎng)度。D(k)[i][j]是從vi到vj的中間頂點(diǎn)的序號(hào)不大于k的最短路徑的長(zhǎng)度。D(n-1)[i][j]就是從vi到vj的最短路徑的長(zhǎng)度。
安全邊:在每一次迭代之前, A是某個(gè)最小生成樹(shù)的一個(gè)子集。在算法的每一步中,確定一條邊(u,v),使得將它加入集合A后,仍然不違反這個(gè)循環(huán)不等式,A U {(u,v)}仍然是某一個(gè)最小生成樹(shù)的子集。稱這樣的邊為A的安全邊。
識(shí)別安全邊的定理:設(shè)圖G=(V,E)是一個(gè)無(wú)向連通圖,并且在E上定義了一個(gè)具有實(shí)數(shù)值的加權(quán)函數(shù)w.設(shè)A是E的一個(gè)子集,它包含于G的某個(gè)最小生成樹(shù)中。設(shè)割(S,V-S)是G的任意一個(gè)不妨害A的割,且邊(u,v)是通過(guò)割(S,V-S)的一條輕邊,則邊(u,v)對(duì)集合A來(lái)說(shuō)是安全的。
推論:設(shè)G=(V,E)是一個(gè)無(wú)向聯(lián)通圖,并且在E上定義了相應(yīng)的實(shí)數(shù)值加權(quán)函數(shù)w.設(shè)A是E的子集,并包含于G的某一最小生成樹(shù)中。設(shè)C=(Vc,Ec)為森林GA=(V,A) 的一個(gè)連通分支。如果邊是連接C和Ga中其他某聯(lián)通分支的一條輕邊,則(u,v)對(duì)集合A來(lái)說(shuō)是安全.
在Kruskal(
克魯斯卡爾)算法和Prim(普里姆)算法
在Kruskal算法中,集合A是一個(gè)森林,加入集合A中的安全邊總是圖中連接兩個(gè)不同聯(lián)通分支的最小權(quán)邊。在Prim算法中,集合A僅形成單棵樹(shù),添加入集合A的安全邊總是連接樹(shù)與一個(gè)不在樹(shù)中的頂點(diǎn)的最小權(quán)邊。
Kruskal(
克魯斯卡爾)算法(O(ElgE)):
該算法找出森林中連接任意兩棵樹(shù)的所有邊中,具有最小權(quán)值的邊(u,v)作為安全邊,并把它添加到正在生長(zhǎng)的森林中。設(shè)C1和C2表示邊(u,v)連接的兩棵樹(shù),因?yàn)?u,v)必是連接C1和其他某棵樹(shù)的一條輕邊,所以由上面推論可知,(u,v)對(duì)C1來(lái)說(shuō)是安全邊。Kruskal 算法同時(shí)也是一種貪心算法, 因?yàn)樵谒惴ǖ拿恳徊街校砑拥缴种械倪叺臋?quán)值都是盡可能小的。
下面是偽代碼:
MST-KRUSKAL(G,w)
A<--空集
for each vertex v 屬于 V[G]
do MAKE-SET(v)
sort the edges of E into nondecreasing order by weight w
for each edge(u,v)屬于E,taken in nondecreasing order by weight
do if FIND-SET(u)!=FIND-SET(v)
then A<--AU{(u,v)}
UNION(u,v)
return A
FIND-SET(u)返回包含u的集合中的一個(gè)代表元素。于是通過(guò)測(cè)試FIND-SET(u)是否等同于FIND-SET(v),就可以確定頂點(diǎn)u和v是否屬于同一棵樹(shù)。通過(guò)過(guò)程UNION,可以實(shí)現(xiàn)樹(shù)與樹(shù)的合并。
Prim算法(O(ElgV))
Prim算法的特點(diǎn)是集合A中的邊總是形成單棵樹(shù)。樹(shù)從任意根頂點(diǎn)r開(kāi)始形成,并逐漸生成,直至該樹(shù)覆蓋了V中的所有頂點(diǎn)。在每一步,一條連接了樹(shù)A與Ga=(V,A)中某孤立頂點(diǎn)的輕邊被加入樹(shù)A中。由推論可知,該規(guī)則僅加入對(duì)A安全的邊,因此當(dāng)算法終止時(shí),A中的邊形成了一棵最小生成樹(shù)。因此每次添加到樹(shù)中的邊都是使樹(shù)的權(quán)盡可能小的邊,因此,上述策略也是“貪心“的。
偽代碼如下:
MST-PRIM(G,w,r)
for each u 屬于V[G]
do key[u] <--空集
n[u]<--NIL
key[r]<--0
Q<--V[G]
while Q!=空集
do u<---EXTRACT-MIN(Q)
for each v屬于Adj[u]
do if v 屬于Q and w(u,v)<key[v]
then n[u]<---u
key[v]<--w(u,v)
參考:算法導(dǎo)論
剛又想起了很多事情,想起了二高的美好時(shí)光,懷念西大,牛逼的2533,一切仿佛都在眼前,我是個(gè)懷舊的人。最近壓力好大,等有時(shí)間了想回西安看看,見(jiàn)見(jiàn)老大,大頭還有魏嵩他們。等回家了也要去二高去看看,高中畢業(yè)后還沒(méi)去過(guò)。
看過(guò)STL空間配置器的源碼,總結(jié)一下:
(1)STL空間配置器:主要分三個(gè)文件實(shí)現(xiàn),stl_construct.h 這里定義了全局函數(shù)construct()和destroy(),負(fù)責(zé)對(duì)象的構(gòu)造和析構(gòu)。stl_alloc.h文件中定義了一,二兩級(jí)配置器,彼此合作,配置器名為alloc. stl_uninitialized.h 這里定義了一些全局函數(shù),用來(lái)填充(fill)或復(fù)制(copy)大塊內(nèi)存數(shù)據(jù),他們也都隸屬于STL標(biāo)準(zhǔn)規(guī)劃。
在stl_alloc.h中定義了兩級(jí)配置器,主要思想是申請(qǐng)大塊內(nèi)存池,小塊內(nèi)存直接從內(nèi)存池中申請(qǐng),當(dāng)不夠用時(shí)再申請(qǐng)新的內(nèi)存池,還有就是大塊內(nèi)存直接申請(qǐng)。當(dāng)申請(qǐng)空間大于128字節(jié)時(shí)調(diào)用第一級(jí)配置器,第一級(jí)配置器沒(méi)有用operator::new和operator::delete來(lái)申請(qǐng)空間,而是直接調(diào)用malloc/free和realloc,并且實(shí)現(xiàn)了類似c++中new-handler的機(jī)制。所謂c++ new handler機(jī)制是,你可以要求系統(tǒng)在內(nèi)存配置需求無(wú)法被滿足時(shí),調(diào)用一個(gè)指定的函數(shù)。換句話說(shuō),一旦::operator::new無(wú)法完成任務(wù),在丟出std::bad_alloc異常狀態(tài)之前,會(huì)先調(diào)用由客端指定的處理例程,該處理例程通常稱為new-handler.new-handler解決內(nèi)存做法有特定的模式。SGI第一級(jí)配置器的allocate()和realloc都是在調(diào)用malloc和realloc不成功后,改調(diào)用oom_malloc()和oom_realloc().后兩者都有內(nèi)循環(huán),不斷調(diào)用"內(nèi)存不足處理例程",期望在某次調(diào)用之后,獲得足夠的內(nèi)存而圓滿完成任務(wù)。但如果“內(nèi)存不足處理例程“并未被客端設(shè)定,oom_malloc()和oom_realloc便調(diào)用_THROW_BAD_ALLOC, 丟出bad_alloc異常信息,或利用exit(1)硬生生中止程序。
在stl_alloc.h中定義的第二級(jí)配置器中,如果區(qū)塊夠大,超過(guò)128字節(jié)時(shí),就移交第一級(jí)配置器處理,當(dāng)區(qū)塊小于128字節(jié)時(shí),則以內(nèi)存池管理,此法又稱為次層配置,每次配置一大塊內(nèi)存,并維護(hù)對(duì)應(yīng)的自由鏈表(free-list).下次若再有相同大小的內(nèi)存需求,就直接從free-list中拔出。如果客端釋還小額區(qū)塊,就由配置器回收到free-lists中,配置器除了負(fù)責(zé)配置,也負(fù)責(zé)回收。為了管理方便,SGI第二級(jí)配置器會(huì)主動(dòng)將任何小額區(qū)塊的內(nèi)存需求量上調(diào)至8的倍數(shù)。并維護(hù)16個(gè)free-lists,各自管理大小分別為8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字節(jié)的小額區(qū)塊。當(dāng)申請(qǐng)小于等于128字節(jié)時(shí)就會(huì)檢查對(duì)應(yīng)的free list,如果free-list中有可用的區(qū)塊,就直接拿來(lái),如果沒(méi)有,就準(zhǔn)備為對(duì)應(yīng)的free-list 重新填充空間。新的空間將取自內(nèi)存池,缺省取得20個(gè)新節(jié)點(diǎn),如果內(nèi)存池不足(還足以一個(gè)以上的節(jié)點(diǎn)),就返回的相應(yīng)的節(jié)點(diǎn)數(shù).如果當(dāng)內(nèi)存池中連一個(gè)節(jié)點(diǎn)大小都不夠時(shí),就申請(qǐng)新的內(nèi)存池,大小為2*total_bytes+ROUND_UP(heap_size>>4), totoal_bytes 為申請(qǐng)的空間大小,ROUND_UP調(diào)整為8的倍數(shù),heap_size為當(dāng)前總申請(qǐng)內(nèi)存池的大小。如果申請(qǐng)?jiān)搩?nèi)存池成功就把原來(lái)內(nèi)存池中剩下的空間分配給適當(dāng)?shù)膄ree-list.萬(wàn)一山窮水盡,整個(gè)system heap空間都不夠了(以至無(wú)法為內(nèi)存池注入源頭活水),malloc()行動(dòng)失敗,就會(huì)四處尋找有無(wú)"尚有未用區(qū)塊,且區(qū)塊足夠大 "之free lists.找到了就挖一塊交出,找不到就調(diào)用第一級(jí)配置器。第一級(jí)配置器其實(shí)也是使用malloc來(lái)配置內(nèi)存。但它有out-of-memory處理機(jī)制(類似new-handler機(jī)制),或許有機(jī)會(huì)釋放其他的內(nèi)存拿來(lái)此處使用。如果可以就成功,否則發(fā)出bad_alloc異常。
參考:STL源碼分析
虛函數(shù)是在類中被聲明為virtual的成員函數(shù),當(dāng)編譯器看到通過(guò)指針或引用調(diào)用此類函數(shù)時(shí),對(duì)其執(zhí)行晚綁定,即通過(guò)指針(或引用)指向的類的類型信息來(lái)決定該函數(shù)是哪個(gè)類的。通常此類指針或引用都聲明為基類的,它可以指向基類或派生類的對(duì)象。
多態(tài)指同一個(gè)方法根據(jù)其所屬的不同對(duì)象可以有不同的行為(根據(jù)自己理解,不知這么說(shuō)是否嚴(yán)謹(jǐn))。
舉個(gè)例子說(shuō)明虛函數(shù)、多態(tài)、早綁定和晚綁定:
李氏兩兄妹(哥哥和妹妹)參加姓氏運(yùn)動(dòng)會(huì)(不同姓氏組隊(duì)參加),哥哥男子項(xiàng)目比賽,妹妹參加女子項(xiàng)目比賽,開(kāi)幕式有一個(gè)參賽隊(duì)伍代表發(fā)言儀式,兄妹倆都想
去露露臉,可只能一人去,最終他們決定到時(shí)抓鬮決定,而組委會(huì)也不反對(duì),它才不關(guān)心是哥哥還是妹妹來(lái)發(fā)言,只要派一個(gè)姓李的來(lái)說(shuō)兩句話就行。運(yùn)動(dòng)會(huì)如期舉
行,妹妹抓鬮獲得代表李家發(fā)言的機(jī)會(huì),哥哥參加了男子項(xiàng)目比賽,妹妹參加了女子項(xiàng)目比賽。比賽結(jié)果就不是我們關(guān)心的了。
現(xiàn)在讓我們來(lái)做個(gè)類比(只討論與運(yùn)動(dòng)會(huì)相關(guān)的話題):
(1)類的設(shè)計(jì):
李氏兄妹屬于李氏家族,李氏是基類(這里還是抽象的純基類),李氏又派生出兩個(gè)子類(李氏男和李氏女),李氏男會(huì)所有男子項(xiàng)目的比賽(李氏男的成員函
數(shù)),李氏女會(huì)所有女子項(xiàng)目的比賽(李氏女的成員函數(shù))。姓李的人都會(huì)發(fā)言(基類虛函數(shù)),李氏男和李氏女繼承自李氏當(dāng)然也會(huì)發(fā)言,只是男女說(shuō)話聲音不一
樣,內(nèi)容也會(huì)又差異,給人感覺(jué)不同(李氏男和李氏女分別重新定義發(fā)言這個(gè)虛函數(shù))。李氏兩兄妹就是李氏男和李氏女兩個(gè)類的實(shí)體。
(2)程序設(shè)計(jì):
李氏兄妹填寫參賽報(bào)名表。
(3)編譯:
李氏兄妹的參賽報(bào)名表被上交給組委會(huì)(編譯器),哥哥和妹妹分別參加男子和女子的比賽,組委會(huì)一看就明白了(早綁定),只是發(fā)言人選不明確,組委會(huì)看到報(bào)
名表上寫的是“李家代表”(基類指針),組委會(huì)不能確定到底是誰(shuí),就做了個(gè)備注:如果是男的,就是哥哥李某某;如果是女的,就是妹妹李某某(晚綁定)。組
委會(huì)做好其它準(zhǔn)備工作后,就等運(yùn)動(dòng)會(huì)開(kāi)始了(編譯完畢)。
(4)程序運(yùn)行:
運(yùn)動(dòng)會(huì)開(kāi)始了(程序開(kāi)始運(yùn)行),開(kāi)幕式上我們聽(tīng)到了李家妹妹的發(fā)言,如果是哥哥運(yùn)氣好抓鬮勝出,我們將聽(tīng)到哥哥的發(fā)言(多態(tài))。然后就是看到兄妹倆參加比賽了。。。
但愿這個(gè)比喻說(shuō)清楚了虛函數(shù)、多態(tài)、早綁定和晚綁定的概念和它們之間的關(guān)系。再說(shuō)一下,早綁定指編譯器在編譯期間即知道對(duì)象的具體類型并確定此對(duì)象調(diào)用成員函數(shù)的確切地址;而晚綁定是根據(jù)指針?biāo)笇?duì)象的類型信息得到類的虛函數(shù)表指針進(jìn)而確定調(diào)用成員函數(shù)的確切地址。
2、揭密晚綁定的秘密
編譯器到底做了什么實(shí)現(xiàn)的虛函數(shù)的晚綁定呢?我們來(lái)探個(gè)究竟。
編譯器對(duì)每個(gè)包含虛函數(shù)的類創(chuàng)建一個(gè)表(稱為V TA B L E)。在V TA B L E中,編譯器放置特定類的虛函數(shù)地址。在每個(gè)帶有虛函數(shù)的類
中,編譯器秘密地置一指針,稱為v p o i n t e r(縮寫為V P T R),指向這個(gè)對(duì)象的V TA B L E。通過(guò)基類指針做虛函數(shù)調(diào) 用時(shí)(也就是做多態(tài)調(diào)用時(shí)),編譯器靜態(tài)地插入取得這個(gè)V P T R,并在V TA B L E表中查找函數(shù)地址的代碼,這樣就能調(diào)用正確的函數(shù)使晚捆綁發(fā)生。為每個(gè)類設(shè)置V TA B L E、初始化V P T R、為虛函數(shù)調(diào)用插入代碼,所有這些都是自動(dòng)發(fā)生的,所以我們不必?fù)?dān)心這些。利用虛函數(shù), 這個(gè)對(duì)象的合適的函數(shù)就能被調(diào)用,哪怕在編譯器還不知道這個(gè)對(duì)象的特定類型的情況下。(《C++編程思想》)
————這段話紅色加粗部分似乎有點(diǎn)問(wèn)題,我個(gè)人的理解看后面的總結(jié)。
在任何類中不存在顯示的類型信息,可對(duì)象中必須存放類信息,否則類型不可能在運(yùn)行時(shí)建立。那這個(gè)類信息是什么呢?我們來(lái)看下面幾個(gè)類:
class no_virtual
{
public:
void fun1() const{}
int fun2() const { return a; }
private:
int a;
}
class one_virtual
{
public:
virtual void fun1() const{}
int fun2() const { return a; }
private:
int a;
}
class two_virtual
{
public:
virtual void fun1() const{}
virtual int fun2() const { return a; }
private:
int a;
}
以上三個(gè)類中:
no_virtual沒(méi)有虛函數(shù),sizeof(no_virtual)=4,類no_virtual的長(zhǎng)度就是其成員變量整型a的長(zhǎng)度;
one_virtual有一個(gè)虛函數(shù),sizeof(one_virtual)=8;
two_virtual
有兩個(gè)虛函數(shù),sizeof(two_virtual)=8; 有一個(gè)虛函數(shù)和兩個(gè)虛函數(shù)的類的長(zhǎng)度沒(méi)有區(qū)別,其實(shí)它們的長(zhǎng)度就是no_virtual的
長(zhǎng)度加一個(gè)void指針的長(zhǎng)度,它反映出,如果有一個(gè)或多個(gè)虛函數(shù),編譯器在這個(gè)結(jié)構(gòu)中插入一個(gè)指針( V P T R)。在one_virtual 和
two_virtual之間沒(méi)有區(qū)別。這是因?yàn)閂 P T R指向一個(gè)存放地址的表,只需要一個(gè)指針,因?yàn)樗刑摵瘮?shù)地址都包含在這個(gè)表中。
這個(gè)VPTR就可以看作類的類型信息。
那我們來(lái)看看編譯器是怎么建立VPTR指向的這個(gè)虛函數(shù)表的。先看下面兩個(gè)類:
class base
{
public:
void bfun(){}
virtual void vfun1(){}
virtual int vfun2(){}
private:
int a;
}
class derived : public base
{
public:
void dfun(){}
virtual void vfun1(){}
virtual int vfun3(){}
private:
int b;
}
兩個(gè)類VPTR指向的虛函數(shù)表(VTABLE)分別如下:
base類
——————
VPTR——> |&base::vfun1 |
——————
|&base::vfun2 |
——————
derived類
———————
VPTR——> |&derived::vfun1 |
———————
|&base::vfun2 |
———————
|&derived::vfun3 |
———————
每當(dāng)創(chuàng)建一個(gè)包含有虛函數(shù)的類或從包含有虛函數(shù)的類派生一個(gè)類時(shí),編譯器就為這個(gè)類創(chuàng)建一個(gè)VTABLE,如上圖所示。在這個(gè)表中,編譯器放置了在這個(gè)類
中或在它的基類中所有已聲明為virtual的函數(shù)的地址。如果在這個(gè)派生類中沒(méi)有對(duì)在基類中聲明為virtual的函數(shù)進(jìn)行重新定義,編譯器就使用基類
的這個(gè)虛函數(shù)地址。(在derived的VTABLE中,vfun2的入口就是這種情況。)然后編譯器在這個(gè)類中放置VPTR。當(dāng)使用簡(jiǎn)單繼承時(shí),對(duì)于每
個(gè)對(duì)象只有一個(gè)VPTR。VPTR必須被初始化為指向相應(yīng)的VTABLE,這在構(gòu)造函數(shù)中發(fā)生。
一旦VPTR被初始化為指向相應(yīng)的VTABLE,對(duì)象就"知道"它自己是什么類型。但只有當(dāng)虛函數(shù)被調(diào)用時(shí)這種自我認(rèn)知才有用。
個(gè)人總結(jié)如下:
1、從包含虛函數(shù)的類派生一個(gè)類時(shí),編譯器就為該類創(chuàng)建一個(gè)VTABLE。其每一個(gè)表項(xiàng)是該類的虛函數(shù)地址。
2、在定義該派生類對(duì)象時(shí),先調(diào)用其基類的構(gòu)造函數(shù),然后再初始化VPTR,最后再調(diào)用派生類的構(gòu)造函數(shù)(
從二進(jìn)制的視野來(lái)看,所謂基類子類是一個(gè)大結(jié)構(gòu)體,其中this指針開(kāi)頭的四個(gè)字節(jié)存放虛函數(shù)表頭指針。執(zhí)行子類的構(gòu)造函數(shù)的時(shí)候,首先調(diào)用基類構(gòu)造函
數(shù),this指針作為參數(shù),在基類構(gòu)造函數(shù)中填入基類的vptr,然后回到子類的構(gòu)造函數(shù),填入子類的vptr,覆蓋基類填入的vptr。如此以來(lái)完成
vptr的初始化。 )
3、在實(shí)現(xiàn)動(dòng)態(tài)綁定時(shí),不能直接采用類對(duì)象,而一定要采用指針或者引用。因?yàn)椴捎妙悓?duì)象傳值方式,有臨時(shí)基類對(duì)象的產(chǎn)生,而采用指針,則是通過(guò)指針來(lái)訪問(wèn)外部的派生類對(duì)象的VPTR來(lái)達(dá)到訪問(wèn)派生類虛函數(shù)的結(jié)果。
VPTR
常常位于對(duì)象的開(kāi)頭,編譯器能很容易地取到VPTR的值,從而確定VTABLE的位置。VPTR總指向VTABLE的開(kāi)始地址,所有基類和它的子類的虛函
數(shù)地址(子類自己定義的虛函數(shù)除外)在VTABLE中存儲(chǔ)的位置總是相同的,如上面base類和derived類的VTABLE中vfun1和vfun2
的地址總是按相同的順序存儲(chǔ)。編譯器知道vfun1位于VPTR處,vfun2位于VPTR+1處,因此在用基類指針調(diào)用虛函數(shù)時(shí),編譯器首先獲取指針指
向?qū)ο蟮念愋托畔ⅲ╒PTR),然后就去調(diào)用虛函數(shù)。如一個(gè)base類指針pBase指向了一個(gè)derived對(duì)象,那pBase->vfun2
()被編譯器翻譯為 VPTR+1 的調(diào)用,因?yàn)樘摵瘮?shù)vfun2的地址在VTABLE中位于索引為1的位置上。同理,pBase->vfun3
()被編譯器翻譯為 VPTR+2的調(diào)用。這就是所謂的晚綁定。
我們來(lái)看一下虛函數(shù)調(diào)用的匯編代碼,以加深理解。
void test(base* pBase)
{
pBase->vfun2();
}
int main(int argc, char* argv[])
{
derived td;
test(&td);
return 0;
}
derived td;編譯生成的匯編代碼如下:
mov DWORD PTR _td$[esp+24], OFFSET FLAT:??_7derived@@6B@ ; derived::`vftable'
由編譯器的注釋可知,此時(shí)PTR _td$[esp+24]中存儲(chǔ)的就是derived類的VTABLE地址。
test(&td);編譯生成的匯編代碼如下:
lea eax, DWORD PTR _td$[esp+24]
mov DWORD PTR __$EHRec$[esp+32], 0
push eax
call ?test@@YAXPAVbase@@@Z ; test
調(diào)用test函數(shù)時(shí)完成了如下工作:取對(duì)象td的地址,將其壓棧,然后調(diào)用test。
pBase->vfun2();編譯生成的匯編代碼如下:
mov ecx, DWORD PTR _pBase$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
首先從棧中取出pBase指針指向的對(duì)象地址賦給ecx,然后取對(duì)象開(kāi)頭的指針變量中的地址賦給eax,此時(shí)eax的值即為VPTR的值,也就是
VTABLE的地址。最后就是調(diào)用虛函數(shù)了,由于vfun2位于VTABLE的第二個(gè)位置,相當(dāng)于 VPTR+1,每個(gè)函數(shù)指針是4個(gè)字節(jié)長(zhǎng),所以最后的
調(diào)用被編譯器翻譯為 jmp DWORD PTR [eax+4]。如果是調(diào)用pBase->vfun1(),這句就該被編譯為
jmp DWORD PTR [eax]。
現(xiàn)在應(yīng)該對(duì)多態(tài)、虛函數(shù)、晚綁定有比較清楚的了解了吧。
轉(zhuǎn)貼:http://blog.csdn.net/shenmea00000/archive/2007/10/31/1859762.aspx
轉(zhuǎn)載:http://www.shnenglu.com/converse/archive/2007/08/29/31179.html
/********************************************************************
created: 2007/08/28
filename: avltree.c
author: Lichuang

purpose: AVL樹(shù)的實(shí)現(xiàn)代碼,
參考資料<<數(shù)據(jù)結(jié)構(gòu)與算法分析-C語(yǔ)言描述>>, 作者Allen Weiss
*********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct AVLTree
{
int nData;
struct AVLTree* pLeft;
struct AVLTree* pRight;
int nHeight;
}AVLTree;

int Max(int a, int b);
int Height(AVLTree* pNode);
AVLTree* Insert(int nData, AVLTree* pNode);
AVLTree* SingleRotateWithLeft(AVLTree* pNode);
AVLTree* SingleRotateWithRight(AVLTree* pNode);
AVLTree* DoubleRotateWithLeft(AVLTree* pNode);
AVLTree* DoubleRotateWithRight(AVLTree* pNode);
void DeleteTree(AVLTree** ppRoot);
void PrintTree(AVLTree* pRoot);

int main()
{
int i;
AVLTree* pRoot = NULL;

srand((unsigned int)::time(NULL));
for (i = 0; i < 100000000; ++i)
{
pRoot = Insert(::rand(), pRoot);
}

//PrintTree(pRoot);

DeleteTree(&pRoot);

return 0;
}

int Max(int a, int b)
{
return (a > b ? a : b);
}

int Height(AVLTree* pNode)
{
if (NULL == pNode)
return -1;

return pNode->nHeight;
}

AVLTree* Insert(int nData, AVLTree* pNode)
{
if (NULL == pNode)
{
pNode = (AVLTree*)malloc(sizeof(AVLTree));
pNode->nData = nData;
pNode->nHeight = 0;
pNode->pLeft = pNode->pRight = NULL;
}
else if (nData < pNode->nData) // 插入到左子樹(shù)中
{
pNode->pLeft = Insert(nData, pNode->pLeft);
if (Height(pNode->pLeft) - Height(pNode->pRight) == 2) // AVL樹(shù)不平衡
{
if (nData < pNode->pLeft->nData)
{
// 插入到了左子樹(shù)左邊, 做單旋轉(zhuǎn)
pNode = SingleRotateWithLeft(pNode);
}
else
{
// 插入到了左子樹(shù)右邊, 做雙旋轉(zhuǎn)
pNode = DoubleRotateWithLeft(pNode);
}
}
}
else if (nData > pNode->nData) // 插入到右子樹(shù)中
{
pNode->pRight = Insert(nData, pNode->pRight);
if (Height(pNode->pRight) - Height(pNode->pLeft) == 2) // AVL樹(shù)不平衡
{
if (nData > pNode->pRight->nData)
{
// 插入到了右子樹(shù)右邊, 做單旋轉(zhuǎn)
pNode = SingleRotateWithRight(pNode);
}
else
{
// 插入到了右子樹(shù)左邊, 做雙旋轉(zhuǎn)
pNode = DoubleRotateWithRight(pNode);
}
}
}

pNode->nHeight = Max(Height(pNode->pLeft), Height(pNode->pRight)) + 1;

return pNode;
}

/********************************************************************
pNode pNode->pLeft
/ \
pNode->pLeft ==> pNode
\ /
pNode->pLeft->pRight pNode->pLeft->pRight
*********************************************************************/
AVLTree* SingleRotateWithLeft(AVLTree* pNode)
{
AVLTree* pNode1;

pNode1 = pNode->pLeft;
pNode->pLeft = pNode1->pRight;
pNode1->pRight = pNode;

// 結(jié)點(diǎn)的位置變了, 要更新結(jié)點(diǎn)的高度值
pNode->nHeight = Max(Height(pNode->pLeft), Height(pNode->pRight)) + 1;
pNode1->nHeight = Max(Height(pNode1->pLeft), pNode->nHeight) + 1;

return pNode1;
}

/********************************************************************
pNode pNode->pRight
\ /
pNode->pRight ==> pNode
/ \
pNode->pRight->pLeft pNode->pRight->pLeft
*********************************************************************/
AVLTree* SingleRotateWithRight(AVLTree* pNode)
{
AVLTree* pNode1;

pNode1 = pNode->pRight;
pNode->pRight = pNode1->pLeft;
pNode1->pLeft = pNode;

// 結(jié)點(diǎn)的位置變了, 要更新結(jié)點(diǎn)的高度值
pNode->nHeight = Max(Height(pNode->pLeft), Height(pNode->pRight)) + 1;
pNode1->nHeight = Max(Height(pNode1->pRight), pNode->nHeight) + 1;

return pNode1;
}

AVLTree* DoubleRotateWithLeft(AVLTree* pNode)
{
pNode->pLeft = SingleRotateWithRight(pNode->pLeft);

return SingleRotateWithLeft(pNode);
}

AVLTree* DoubleRotateWithRight(AVLTree* pNode)
{
pNode->pRight = SingleRotateWithLeft(pNode->pRight);

return SingleRotateWithRight(pNode);
}

// 后序遍歷樹(shù)以刪除樹(shù)
void DeleteTree(AVLTree** ppRoot)
{
if (NULL == ppRoot || NULL == *ppRoot)
return;

DeleteTree(&((*ppRoot)->pLeft));
DeleteTree(&((*ppRoot)->pRight));
free(*ppRoot);
*ppRoot = NULL;
}

// 中序遍歷打印樹(shù)的所有結(jié)點(diǎn), 因?yàn)樽蠼Y(jié)點(diǎn) < 父結(jié)點(diǎn) < 右結(jié)點(diǎn), 因此打印出來(lái)數(shù)據(jù)的大小是遞增的
void PrintTree(AVLTree* pRoot)
{
if (NULL == pRoot)
return;

static int n = 0;

PrintTree(pRoot->pLeft);
printf("[%d]nData = %d\n", ++n, pRoot->nData);
PrintTree(pRoot->pRight);
}

聽(tīng)著舒伯特和肖邦的小夜曲,兩種不同的風(fēng)格,聽(tīng)了都特別的舒服,感覺(jué)特別地寧?kù)o,讓我浮躁的心能安靜下來(lái),說(shuō)不上好在哪里,可能比較符合我現(xiàn)在的心境吧,或許我生活中還缺少某種元素。
以STL的運(yùn)用角度而言,空間配置器時(shí)最不需要介紹的東西,它總是隱藏在一切組件的背后,整個(gè)STL的操作對(duì)象(所有的數(shù)值)都存放在容器之內(nèi),而容器一定需要配置空間以置放資料。下面是一個(gè)簡(jiǎn)單空間配置器代碼(來(lái)自 STL源碼剖析):
//jjalloc.h
#ifndef _JJALLOC_
#define _JJALLOC_
#include<new>
#include<cstddef>
#include<cstdlib>
#include<climits>
#include<iostream>
using namespace std;
namespace JJ
{
template<class T>
inline T* _allocate(ptrdiff_t size,T*)
{
set_new_handler(0);
T *tmp=(T*)(::operator new((size_t)(size* sizeof(T))));
if(tmp==0){
cerr<<"out of memory"<<endl;
exit(1);
}
return tmp;
}
template<class T>
inline void _deallocate(T* buffer)
{
::operator delete(buffer);
}
template<class T1,class T2>
inline void _construct(T1 *p,const T2& value)
{
new(p)T1(value);//placement new operator
}
template<class T>
inline void _destroy(T* ptr)
{
ptr->~T();
}
template<class T>class allocator{
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
template<class U>
struct rebind
{
typedef allocator<U>other;
};
pointer allocate(size_type n,const void * hint=0)
{
return _allocate((difference_type)n,(pointer)0);
}
void deallocate(pointer p,size_type n)
{
_deallocate(p);
}
void construct(pointer p,const T& value)
{
_construct(p,value);
}
void destroy(pointer p){_destroy(p);}
pointer address(reference x){return (pointer)&x;}
const_pointer const_address(const_reference x)
{
return (const_pointer)&x;
}
size_type max_size()const{
return size_type(UINT_MAX/sizeof(T));
}
};
}//end of namespace JJ
#endif
//jjalloc.cc,測(cè)試上面這個(gè)簡(jiǎn)單的配置器
#include"jjalloc.h"
#include<vector>
#include<iostream>
using namespace std;
int main()
{
int ia[5]={0,1,2,3,4};
unsigned int i;
vector<int,JJ::allocator<int> >iv(ia,ia+5);
for(i=0;i<iv.size();i++)
cout<<iv[i]<<' ';
cout<<endl;
}