《0bug-C/C++商用工程之道》节选00--内存管理的基本要求
最近被朋友們老是問到一些嵌入式的問題,很多是技術(shù)底層細(xì)節(jié)的問題,很不好回答,因?yàn)樯婕暗郊夹g(shù),要么不寫,要寫就要長篇大論,太費(fèi)精力,也不太適合在網(wǎng)上上討論。
想了一下,干脆這樣,我將在博客上不定期、部分公布我的《0bug-C/C++商用工程之道》一書中的章節(jié)段落,有興趣的朋友可以看看。
其實(shí)回過頭再看這本書,感覺這一年多做數(shù)據(jù)庫,對于書中很多技術(shù)又有了不少新的看法,在公布的過程中,我也會(huì)將自己新的一些觀點(diǎn)補(bǔ)充一下,也歡迎有興趣的朋友,針對技術(shù)觀點(diǎn),發(fā)郵件討論。
我的QQ是712123,常用的郵箱是tonyxiaohome@hotmail.com
在考量這個(gè)分享內(nèi)容的時(shí)候,我想首先從內(nèi)存討論開始吧,因?yàn)槲矣X得作為C語言程序員,無論是在哪個(gè)平臺(tái)開發(fā),對于內(nèi)存的掌控是必須的,可以說是重中之重的技術(shù)。
一切從內(nèi)存開始!
所以我挑選了一下,從第七章《內(nèi)存及資源管理》開始分享。
C語言是公認(rèn)的一門中低級語言,主要的原因就是其提供了類似于匯編語言的指針調(diào)用,將編譯器和操作系統(tǒng)內(nèi)部的很多核心機(jī)密,向應(yīng)用程序公開,使我們的程序,可以自由地使用動(dòng)態(tài)內(nèi)存申請,指針管理等操作系統(tǒng)級的功能,實(shí)現(xiàn)強(qiáng)大的程序能力。
這實(shí)際上是把操作系統(tǒng)對內(nèi)存的管理,向程序員做了公開,程序員可以站在系統(tǒng)的角度,進(jìn)行動(dòng)態(tài)的內(nèi)存資源調(diào)度,這給C和C++程序員帶來了莫大的方便性,獲得了強(qiáng)大的控制能力,但同時(shí),也給C和C++程序帶來了天生的安全隱患。
因此,作為一個(gè)商業(yè)化的C和C++程序員,首先就需要熟練掌握對內(nèi)存,以及各種系統(tǒng)資源的操作能力,能做到不泄露、不溢出、安全使用。這對程序員的綜合實(shí)力,提出了較高的要求。筆者在本章,將為大家展示對系統(tǒng)內(nèi)存,以及各個(gè)系統(tǒng)資源實(shí)施管控的綜合技巧和原則。
7.1? 內(nèi)存管理的基本要求
其實(shí)很多高級語言,如Java、Python,都有自己的內(nèi)存管理器,應(yīng)用程序一般盡管使用變量即可,程序員很少關(guān)心變量失效之后的摧毀問題,更無需關(guān)心內(nèi)存的優(yōu)化使用,減少內(nèi)存碎片等細(xì)節(jié)。
不過,C和C++語言,把系統(tǒng)內(nèi)存直接暴露給程序員使用,看似提升了靈活性和方便性,但同時(shí),也放棄了更高級的內(nèi)存管控機(jī)制,這對程序員提出了很高的理論和實(shí)戰(zhàn)能力的要求,稍有不慎,即會(huì)出現(xiàn)bug。
筆者經(jīng)過多年的分析,認(rèn)為如果要徹底杜絕內(nèi)存相關(guān)的bug,實(shí)現(xiàn)C和C++語言無錯(cuò)化程序設(shè)計(jì),程序員有必要在C和C++提供的基本內(nèi)存操作的基礎(chǔ)上,自行構(gòu)建一個(gè)更加合理的內(nèi)存管理機(jī),幫助程序員實(shí)現(xiàn)內(nèi)存的安全、高效訪問。
這個(gè)內(nèi)存管理機(jī),筆者稱之為“內(nèi)存池”,本小節(jié)即試圖論述其基本的設(shè)計(jì)需求以及解決方案。
7.1.1? 不泄露
由于C和C++語言中,內(nèi)存的申請和釋放,一定是二元?jiǎng)幼?#xff0c;需要程序員顯式地調(diào)用相關(guān)函數(shù),對稱地完成內(nèi)存操作,才能保證不泄露內(nèi)存。
這對很多情況下的程序開發(fā),提出了較高的要求,筆者前文花了大量的篇幅,向大家介紹二元?jiǎng)幼鞑僮鞯某R娛址?#xff0c;以期避免內(nèi)存泄漏等bug。
不過,這些動(dòng)作一般都是程序員的行為,我們知道,程序員是人,是人就有可能犯錯(cuò)誤,純粹的手工操作規(guī)范,并不足以杜絕內(nèi)存bug的產(chǎn)生。
于是筆者就設(shè)想,如果在C和C++傳統(tǒng)的內(nèi)存管理機(jī)制之外,我們自行構(gòu)建一種內(nèi)存的管理機(jī)制,能在程序員忘了釋放內(nèi)存時(shí),主動(dòng)替其釋放,則可望大大減少內(nèi)存相關(guān)的bug。因此,內(nèi)存池的第一個(gè)設(shè)計(jì)目標(biāo),是主動(dòng)替程序員完善二元?jiǎng)幼?#xff0c;確保“不泄露內(nèi)存”。
針對這個(gè)問題,筆者通常的解決方案是內(nèi)部建立一套登記機(jī)制,記錄所有在用的內(nèi)存塊,當(dāng)程序退出時(shí),如果發(fā)現(xiàn)還有內(nèi)存塊在內(nèi)存池中處于激活狀態(tài),即表示有內(nèi)存塊忘了釋放,內(nèi)存池會(huì)幫助程序員釋放內(nèi)存,避免產(chǎn)生內(nèi)存泄漏。
7.1.2? 不產(chǎn)生碎片
前文我們已經(jīng)說過,對于7*24小時(shí)運(yùn)行的服務(wù)器和嵌入式設(shè)備,其對內(nèi)存的管理要求很高,僅僅不泄露內(nèi)存是不夠的,還必須保證無內(nèi)存碎片的產(chǎn)生,確保內(nèi)存池可以長期有效地提供服務(wù)。
從前文我們知道,內(nèi)存碎片的根源,在于一個(gè)程序,無序地申請任意大小的內(nèi)存,最后導(dǎo)致系統(tǒng)堆上的內(nèi)存不連貫,雖然從統(tǒng)計(jì)上得知,還有足夠的內(nèi)存空間,但這是由小塊的內(nèi)存區(qū)域組成,沒有足夠的,整塊的大型空間,使后續(xù)的大塊內(nèi)存申請無法成功,導(dǎo)致服務(wù)無法繼續(xù)。
這個(gè)問題比較難解決,很多解釋型高級語言,可以在運(yùn)行前,主動(dòng)分析程序,對大型的內(nèi)存申請實(shí)行預(yù)分配制度,但是,在C和C++里面,由于是編譯執(zhí)行,動(dòng)態(tài)內(nèi)存申請又過于靈活,很多內(nèi)存塊的尺寸取決與中間計(jì)算結(jié)果,因此,無法實(shí)現(xiàn)預(yù)分配,內(nèi)存的申請和釋放動(dòng)作完全依賴應(yīng)用程序自己的設(shè)計(jì),無法實(shí)現(xiàn)統(tǒng)一管控。
筆者經(jīng)過思考,總結(jié)了如下幾條推論,以此來試圖控制內(nèi)存碎片的產(chǎn)生:
| 1、一個(gè)應(yīng)用程序,總的來說,其使用的所有內(nèi)存,不會(huì)超過其目標(biāo)運(yùn)行平臺(tái)的基本內(nèi)存空間,這很好理解,每個(gè)程序員做程序,總是能預(yù)估自己的程序,需要多大內(nèi)存,因此,在產(chǎn)品說明書上,會(huì)提示用戶準(zhǔn)備足夠內(nèi)存的計(jì)算機(jī)。 2、由上可知,我們在動(dòng)態(tài)內(nèi)存塊可以重用的前提下,可以利用一套機(jī)制,屏蔽內(nèi)存區(qū)的動(dòng)態(tài)釋放工作,即所有的動(dòng)態(tài)內(nèi)存,一旦申請,在本次運(yùn)行期不再釋放,這并不會(huì)導(dǎo)致計(jì)算機(jī)內(nèi)存溢出。 3、動(dòng)態(tài)內(nèi)存塊可以重用,需要保證兩點(diǎn),首先是有一套管理機(jī)制,可以記錄申請和釋放的所有內(nèi)存塊,把釋放的內(nèi)存塊二次提供給新的內(nèi)存申請使用,其次,必須對內(nèi)存塊取模,減小內(nèi)存塊的種類,提高內(nèi)存塊的可重用性。關(guān)于取模的原則和方法,我們后文討論。 |
這樣的話,如果按照上述機(jī)制來設(shè)計(jì)內(nèi)存池,雖然我們應(yīng)用程序在運(yùn)行過程中,存在大量的動(dòng)態(tài)內(nèi)存申請,但就該程序運(yùn)行期總的需求來說,使用的最大內(nèi)存數(shù)并沒有多大變化,在操作系統(tǒng)看來,這個(gè)程序從一開始,申請了差不多的內(nèi)存之后,就不再申請,全部是內(nèi)部重用,自然也就沒有內(nèi)存碎片的產(chǎn)生了。
提示:如果計(jì)算機(jī)系統(tǒng)內(nèi)存太小,這種機(jī)制導(dǎo)致內(nèi)存溢出了,那是說明應(yīng)用程序?qū)ψ约菏褂玫淖畲髢?nèi)存沒有估計(jì)準(zhǔn)確,用戶使用的計(jì)算機(jī)太低檔,正確的解法不是改程序,而是請用戶加大內(nèi)存,或者干脆換更高檔的計(jì)算機(jī)設(shè)備。
7.1.3? 可以自動(dòng)報(bào)警
在解決“不泄露”的問題時(shí),筆者設(shè)計(jì)了一個(gè)內(nèi)存管理鏈表,在設(shè)計(jì)該鏈表的時(shí)候,筆者突發(fā)奇想,由于內(nèi)存池已經(jīng)收攏了所有的內(nèi)存申請和釋放行為,那么,我們自然可以很輕易地知道是哪個(gè)模塊在申請內(nèi)存,為什么不把這個(gè)信息記錄下來,幫助Debug呢?
我們知道,內(nèi)存泄漏問題之所以難以解決,并不是這個(gè)問題有多復(fù)雜,而是由于內(nèi)存申請和釋放,是程序行為,我們只有在最后的程序運(yùn)行中,隱約觀察到內(nèi)存使用在不斷增長,由此推論可能程序有內(nèi)存泄漏,但這種觀察很不直觀,無法幫助程序員精確查找是哪個(gè)模塊發(fā)生了內(nèi)存泄漏。
但如果我們設(shè)計(jì)內(nèi)存池時(shí),要求申請內(nèi)存的模塊,必須注明自己的模塊名,在退出時(shí),一旦檢測出哪個(gè)模塊忘了釋放內(nèi)存,馬上將其申請信息打印出來,不就可以幫助程序員很方便地檢查出發(fā)生內(nèi)存泄漏的模塊嗎?順便,也就能在開發(fā)期快速解決掉所有的內(nèi)存泄漏,徹底杜絕這類bug。
經(jīng)過思考,筆者的內(nèi)存池所有的內(nèi)存申請動(dòng)作,全部改為如下格式:
| MemPool.Malloc(int nSize,char* szInfo); |
?
這明顯有別于C語言原有的內(nèi)存申請函數(shù):
| malloc(int nSize); |
?
這里的szInfo,是筆者規(guī)定的124Bytes的說明性文字,強(qiáng)迫所有申請內(nèi)存的模塊,必須在其中聲明自身的身份,一旦發(fā)生泄漏,任何一次運(yùn)行完畢,內(nèi)存池析構(gòu)時(shí),立即會(huì)打印出相關(guān)的信息,程序員即可實(shí)現(xiàn)快速查找。
經(jīng)過試用,這樣的效果非常明顯,任何一段程序,只要忘了釋放內(nèi)存,則在第一運(yùn)行結(jié)束時(shí),內(nèi)存池會(huì)自動(dòng)打印報(bào)警信息,信息中標(biāo)明是哪個(gè)模塊的哪個(gè)函數(shù),由于什么原因分配的內(nèi)存塊忘了釋放,程序員幾乎立即就可以找到故障點(diǎn),排除bug。
提示:筆者在前不久帶領(lǐng)團(tuán)隊(duì)開發(fā)的一個(gè)服務(wù)器集群中,由于引入了這類注冊+自報(bào)警機(jī)制,所有的C和C++程序模塊,從未出現(xiàn)內(nèi)存泄漏,極大地提升了程序的穩(wěn)定性,也為項(xiàng)目的順利完成打下了堅(jiān)實(shí)的基礎(chǔ)。
總結(jié)
以上是生活随笔為你收集整理的《0bug-C/C++商用工程之道》节选00--内存管理的基本要求的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux find 用法详解 + 实例
- 下一篇: CloudPaster日志