程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 《0bug-C/C++商用工程之道》節選00--內存管理的基本要求

《0bug-C/C++商用工程之道》節選00--內存管理的基本要求

編輯:關於C語言

最近被朋友們老是問到一些嵌入式的問題,很多是技術底層細節的問題,很不好回答,因為涉及到技術,要麼不寫,要寫就要長篇大論,太費精力,也不太適合在網上上討論。

想了一下,干脆這樣,我將在博客上不定期、部分公布我的《0bug-C/C++商用工程之道》一書中的章節段落,有興趣的朋友可以看看。

其實回過頭再看這本書,感覺這一年多做數據庫,對於書中很多技術又有了不少新的看法,在公布的過程中,我也會將自己新的一些觀點補充一下,也歡迎有興趣的朋友,針對技術觀點,發郵件討論。

我的QQ是712123,常用的郵箱是[email protected]

在考量這個分享內容的時候,我想首先從內存討論開始吧,因為我覺得作為C語言程序員,無論是在哪個平台開發,對於內存的掌控是必須的,可以說是重中之重的技術。

一切從內存開始!

所以我挑選了一下,從第七章《內存及資源管理》開始分享。

C語言是公認的一門中低級語言,主要的原因就是其提供了類似於匯編語言的指針調用,將編譯器和操作系統內部的很多核心機密,向應用程序公開,使我們的程序,可以自由地使用動態內存申請,指針管理等操作系統級的功能,實現強大的程序能力。

這實際上是把操作系統對內存的管理,向程序員做了公開,程序員可以站在系統的角度,進行動態的內存資源調度,這給C和C++程序員帶來了莫大的方便性,獲得了強大的控制能力,但同時,也給C和C++程序帶來了天生的安全隱患。

因此,作為一個商業化的C和C++程序員,首先就需要熟練掌握對內存,以及各種系統資源的操作能力,能做到不洩露、不溢出、安全使用。這對程序員的綜合實力,提出了較高的要求。筆者在本章,將為大家展示對系統內存,以及各個系統資源實施管控的綜合技巧和原則。

7.1  內存管理的基本要求

其實很多高級語言,如Java、Python,都有自己的內存管理器,應用程序一般盡管使用變量即可,程序員很少關心變量失效之後的摧毀問題,更無需關心內存的優化使用,減少內存碎片等細節。

不過,C和C++語言,把系統內存直接暴露給程序員使用,看似提升了靈活性和方便性,但同時,也放棄了更高級的內存管控機制,這對程序員提出了很高的理論和實戰能力的要求,稍有不慎,即會出現bug。

筆者經過多年的分析,認為如果要徹底杜絕內存相關的bug,實現C和C++語言無錯化程序設計,程序員有必要在C和C++提供的基本內存操作的基礎上,自行構建一個更加合理的內存管理機,幫助程序員實現內存的安全、高效訪問。

這個內存管理機,筆者稱之為“內存池”,本小節即試圖論述其基本的設計需求以及解決方案。

7.1.1  不洩露

由於C和C++語言中,內存的申請和釋放,一定是二元動作,需要程序員顯式地調用相關函數,對稱地完成內存操作,才能保證不洩露內存。

這對很多情況下的程序開發,提出了較高的要求,筆者前文花了大量的篇幅,向大家介紹二元動作操作的常見手法,以期避免內存洩漏等bug。

不過,這些動作一般都是程序員的行為,我們知道,程序員是人,是人就有可能犯錯誤,純粹的手工操作規范,並不足以杜絕內存bug的產生。

於是筆者就設想,如果在C和C++傳統的內存管理機制之外,我們自行構建一種內存的管理機制,能在程序員忘了釋放內存時,主動替其釋放,則可望大大減少內存相關的bug。因此,內存池的第一個設計目標,是主動替程序員完善二元動作,確保“不洩露內存”。

針對這個問題,筆者通常的解決方案是內部建立一套登記機制,記錄所有在用的內存塊,當程序退出時,如果發現還有內存塊在內存池中處於激活狀態,即表示有內存塊忘了釋放,內存池會幫助程序員釋放內存,避免產生內存洩漏。

7.1.2  不產生碎片

前文我們已經說過,對於7*24小時運行的服務器和嵌入式設備,其對內存的管理要求很高,僅僅不洩露內存是不夠的,還必須保證無內存碎片的產生,確保內存池可以長期有效地提供服務。

從前文我們知道,內存碎片的根源,在於一個程序,無序地申請任意大小的內存,最後導致系統堆上的內存不連貫,雖然從統計上得知,還有足夠的內存空間,但這是由小塊的內存區域組成,沒有足夠的,整塊的大型空間,使後續的大塊內存申請無法成功,導致服務無法繼續。

這個問題比較難解決,很多解釋型高級語言,可以在運行前,主動分析程序,對大型的內存申請實行預分配制度,但是,在C和C++裡面,由於是編譯執行,動態內存申請又過於靈活,很多內存塊的尺寸取決與中間計算結果,因此,無法實現預分配,內存的申請和釋放動作完全依賴應用程序自己的設計,無法實現統一管控。

筆者經過思考,總結了如下幾條推論,以此來試圖控制內存碎片的產生:

1、一個應用程序,總的來說,其使用的所有內存,不會超過其目標運行平台的基本內存空間,這很好理解,每個程序員做程序,總是能預估自己的程序,需要多大內存,因此,在產品說明書上,會提示用戶准備足夠內存的計算機。

2、由上可知,我們在動態內存塊可以重用的前提下,可以利用一套機制,屏蔽內存區的動態釋放工作,即所有的動態內存,一旦申請,在本次運行期不再釋放,這並不會導致計算機內存溢出。

3、動態內存塊可以重用,需要保證兩點,首先是有一套管理機制,可以記錄申請和釋放的所有內存塊,把釋放的內存塊二次提供給新的內存申請使用,其次,必須對內存塊取模,減小內存塊的種類,提高內存塊的可重用性。關於取模的原則和方法,我們後文討論。

這樣的話,如果按照上述機制來設計內存池,雖然我們應用程序在運行過程中,存在大量的動態內存申請,但就該程序運行期總的需求來說,使用的最大內存數並沒有多大變化,在操作系統看來,這個程序從一開始,申請了差不多的內存之後,就不再申請,全部是內部重用,自然也就沒有內存碎片的產生了。

提示:如果計算機系統內存太小,這種機制導致內存溢出了,那是說明應用程序對自己使用的最大內存沒有估計准確,用戶使用的計算機太低檔,正確的解法不是改程序,而是請用戶加大內存,或者干脆換更高檔的計算機設備。

7.1.3  可以自動報警

在解決“不洩露”的問題時,筆者設計了一個內存管理鏈表,在設計該鏈表的時候,筆者突發奇想,由於內存池已經收攏了所有的內存申請和釋放行為,那麼,我們自然可以很輕易地知道是哪個模塊在申請內存,為什麼不把這個信息記錄下來,幫助Debug呢?

我們知道,內存洩漏問題之所以難以解決,並不是這個問題有多復雜,而是由於內存申請和釋放,是程序行為,我們只有在最後的程序運行中,隱約觀察到內存使用在不斷增長,由此推論可能程序有內存洩漏,但這種觀察很不直觀,無法幫助程序員精確查找是哪個模塊發生了內存洩漏。

但如果我們設計內存池時,要求申請內存的模塊,必須注明自己的模塊名,在退出時,一旦檢測出哪個模塊忘了釋放內存,馬上將其申請信息打印出來,不就可以幫助程序員很方便地檢查出發生內存洩漏的模塊嗎?順便,也就能在開發期快速解決掉所有的內存洩漏,徹底杜絕這類bug。

經過思考,筆者的內存池所有的內存申請動作,全部改為如下格式:

MemPool.Malloc(int nSize,char* szInfo);

 

這明顯有別於C語言原有的內存申請函數:

malloc(int nSize);

 

這裡的szInfo,是筆者規定的124Bytes的說明性文字,強迫所有申請內存的模塊,必須在其中聲明自身的身份,一旦發生洩漏,任何一次運行完畢,內存池析構時,立即會打印出相關的信息,程序員即可實現快速查找。

經過試用,這樣的效果非常明顯,任何一段程序,只要忘了釋放內存,則在第一運行結束時,內存池會自動打印報警信息,信息中標明是哪個模塊的哪個函數,由於什麼原因分配的內存塊忘了釋放,程序員幾乎立即就可以找到故障點,排除bug。

提示:筆者在前不久帶領團隊開發的一個服務器集群中,由於引入了這類注冊+自報警機制,所有的C和C++程序模塊,從未出現內存洩漏,極大地提升了程序的穩定性,也為項目的順利完成打下了堅實的基礎。

本文出自 “肖舸的blog” 博客,請務必保留此出處http://tonyxiaohome.blog.51cto.com/925273/595351

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved