c,c++語言內存管理精華文章_第1頁
c,c++語言內存管理精華文章_第2頁
c,c++語言內存管理精華文章_第3頁
c,c++語言內存管理精華文章_第4頁
c,c++語言內存管理精華文章_第5頁
已閱讀5頁,還剩4頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、內存管理:c/c+技術文檔 從第3部分開始,本文總結了內存的全面知識,乃精華文章。 2012/9/20 合肥工業大學 通信所506 1.c/c+頭文件 共3個組成:1)頭文件開頭處的版權和版本聲明 2)預處理塊 - #ifndef/#define/#endif 防止該頭文件被重復引用,特別是針對處理:全局變量的重 復定義會導致編譯出錯。(不提倡使用全局變量。) 3)函數和類結構聲明。 缺一不可。 如果一個軟件的頭文件數目比較多(如超過十個),通常應將頭文件和定義文件分別保存于不同的目錄,以便于維護。 2.程序的板式:可讀性強,清晰,美觀 1)空行 空行不會浪費內存,只是為了使程序布局更加清晰。

2、但不要過多空行。一般情況下,在每個類聲明之后、每個函數定義結束之后都要加空行。 2)代碼行 一行代碼只做一件事。如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,并且方便于寫注釋。if、for、while、do 等語句自占一行,執行語句不得緊跟其后。不論執行語句有多少都要加。這樣可以防止書寫失誤。 盡可能在定義變量的同時初始化該變量(就近原則)。如果引用了未被初始化的變量,可能會導致程序錯誤。 3)除了函數名以外,關鍵字之后一定要留空格。 3.內存管理 1,內存有三種分配方式:a。從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,sta

3、tic 變量。b。在棧上創建(動態存儲區分配)。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。c。從堆上分配。,亦稱動態內存分配。程序在運行的時候用malloc 或new 申請任意多少的內存,程序員自己負責在何時用free 或delete 釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。 2.常見的內存錯誤: a。內存分配未成功,卻使用了它。編程新手常犯這種錯誤,因為他們沒有意識到內存分配會不成功。常用解決辦法是,在 使用內存之前檢查指針是否為NULL。如果

4、指針p 是函數的參數,那么在函數的入口處用 assert(p!=NULL進行檢查。如果是用malloc 或new 來申請內存,應該用if(p=NULL 或 if(p!=NULL進行防錯處理。 b。內存分配雖然成功,但是尚未初始化就引用它。犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為內存的缺省初值全為 零,導致引用初值錯誤(例 如數組)。內存的缺省初值究竟是什么并沒有統一的標準,盡管有些時候為零值,我們寧可信其無 不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦6零值也不可省略,不要嫌麻煩。 c。內存分配成功并且已經初始化,但操作越過了內存的邊界。 例如在使用數組時

5、經常發生下標“多1”或者“少1”的操作。特別是在for 循環語句中,循環次數很容易搞錯,導致數組操作越界。 d。忘記了釋放內存,造成內存泄露。 含有這種錯誤的函數每被調用一次就丟失一塊內存。剛開始時系統的內存充足,你看不到錯誤。終有一次程序突然死掉,系統出現提示:內存耗盡。動態內存的申請與釋放必須配對,程序中malloc 與free 的使用次數一定要相同,否則肯定有錯誤(new/delete 同理)。 e。釋放了內存卻繼續使用它。 A.程序中的對象調用關系過于復雜,實在難以搞清楚某個對象究竟是否已經釋放了內存, 此時應該重新設計數據結構,從根本上解決對象管理的混亂局面。 B.函數的return

6、 語句寫錯了,注意不要返回指向“棧內存”的“指針”或者“引用”,因 為該內存在函數體結束時被自動銷毀。 C.使用free 或delete 釋放了內存后,沒有將指針設置為NULL。導致產生“野指針. 【規則1】用malloc 或new 申請內存之后,應該立即檢查指針值是否為NULL。防 止使用指針值為NULL 的內存. 【規則2】不要忘記為數組和動態內存賦初值。防止將未被初始化的內存作為右值 使用。 【規則3】避免數組或指針的下標越界,特別要當心發生“多1”或者“少1”操作。 【規則4】動態內存的申請與釋放必須配對,防止內存泄漏。 【規則5】用free 或delete 釋放了內存之后,立即將指針

7、設置為NULL,防止產生 “野指針”。 3.指針與數據的對比。 數組要么在靜態存儲區被創建(如全局數組),要么在棧上被創建。數組名對應著(而 不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改變。 指針可以隨時指向任意類型的內存塊,它的特征是“可變”,所以我們常用指針來操作 動態內存。指針遠比數組靈活,但也更危險。 下面以字符數組為例,字符數組一旦定義,其地址和容量不變,只能改變其內容,靈活性不夠。而字符指針變量則不然,他的靈活很大,可以隨時指向任何類型的內存塊。但是操作需謹慎。 示例7-3-1 中,字符數組a 的容量是6 個字符,其內容為hello0。a 的內容可以改變

8、, 如a0= X。指針p 指向常量字符串“world”(位于靜態存儲區,內容為world0),常量字 符串的內容是不可以被修改的。從語 法上看,編譯器并不覺得語句p0= X有什么不妥, 但是該語句企圖修改常量字符串的內容而導致運行錯誤。 特此聲明: 字符串常量在字符數組和字符指針變量中的賦值涵義不同。在字符數組中存放是按照每個元素存放的,其值可以被修改。因為數組中的每個元素相當于一個變量,變量的值是可以改變的。 但當字符串常量賦給字符指針變量的時候,指針變量會把字符串常量當成一個整體常量來對待,默認為常變量,存放在內存的靜態存儲區,其值在程序運行期間一直保持不變,絕對不能通過指針變量修改其值,

9、但是可以通過字符指針變量的加減輸出不同位置的字符。 內容復制與比較。 不能對數組名進行直接復制與比較。示例7-3-2 中,若想把數組a 的內容復制給數組b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函數strcpy 進行復制。同理,比較b 和a 的內容是否相同,不能用if(b=a 來判斷,應該用標準庫函數strcmp 進行比較。 語句p = a 并不能把a 的內容復制指針p,而是把a 的地址賦給了p。要想復制a 的內容,可以先用庫函數malloc 為p 申請一塊容量為strlen(a+1 個字符的內存,再用strcpy進行字符串復制。同理,語句if(p=a 比較的不是內容而是地

10、址,應該用庫函數strcmp 來比較。 4.計算內存容量 sizeof(數據類型、數組名、變量名、表達式、類名、結構體名、對象名)用來判斷數據類型長度的,即在內存中所占的長度,注意它所計算出來的長度是根據內存對齊原則計算出來的,一定要注意偏移地址的概念,才能正確計算。而strlen(字符串、字符數組名)是用來計算實際字符長度的。二者不同。 用運算符sizeof 可以計算出數組的容量(字節數)。示例7-3-3(a)中,sizeof(a的值是12(注意別忘了0)。指針p 指向a,但是sizeof(p的值卻是4。這是因為sizeof(p得到的是一個指針變量的字節數,相當于sizeof(char*,而

11、不是p 所指的內存容量。C+/C語言沒有辦法知道指針所指的內存容量,除非在申請內存時記住它。 注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。所以sizeof此時的值也是4,指針變量的大小是一個固定的值,不管他賦予了什么值,后面接了什么,都是4. 5.指針參數是如何傳遞內存的? 如果函數的參數是一個指針,不要指望用該指針去申請動態內存。 例子:void GetMemory(char *p, int num p = (char *malloc(sizeof(char * num; void Test(void char *str = NULL; GetMemory(str, 1

12、00; / str 仍然為 NULL strcpy(str, "hello" / 運行錯誤 我們發現,Test函數的語句GetMemory(str, 200并沒有使str 獲得期望的 內存,str 依舊是NULL。 毛病出在函數GetMemory 中。編譯器總是要為函數的每個參數制作臨時副本,指針參數p 的副本是 _p,編譯器使 _p = p。如果函數體內的程序修改了_p 的內容,就導致參數p的內容作相應的修改。這就是指針可以用作輸出參數的原因。在本例中,_p 申請了新的內 存,只是把_p 所指的內存地址改變了,但是p 絲毫未變。所以函數GetMemory 并不能輸出任何東

13、西。事實上,每執行一次GetMemory 就會泄露一塊內存,因為沒有用free 釋放內存。 用函數返回值來傳遞動態內存這種方法雖然好用,但是常常有人把return 語句用錯了。這里強調不要用return 語句返回指向“棧內存”的指針,因為該內存在函數結束時自動消亡。 6.free 和delete 把指針怎么啦? free() 和delete 它們只是用來把指針所指的內存給釋放掉,但并沒有把指針本身干掉。這個指針變量還存在,如果不設定為空指針,他亂指一器,成為了野指針。 用調試器跟蹤,發現指針p 被free 以后其地址仍然不變(非NULL),只是該地址對應的內存是垃圾,p 成了“野指針”。如果此

14、時不把p 設置為NULL,會讓人誤以為p是個合法的指針。 如果程序比較長,我們有時記不住p 所指的內存是否已經被釋放,在繼續使用p 之前通常會用語句if (p != NULL進行防錯處理。很遺憾,此時if 語句起不到防錯作用,因為即便p 不是NULL 指針,它也不指向合法的內存塊。 char *p = (char * malloc(100; strcpy(p, “hello”; free(p; / p 所指的內存被釋放,但是p 所指的地址仍然不變 if(p != NULL / 沒有起到防錯作用 strcpy(p, “world”; / 出錯 7。動態內存會被自動釋放嗎? 函數體內的局部變量在函

15、數結束時自動消亡。但是如果在里面定義了靜態的局部變量或者是字符指針變量則不然。1)(1)指針消亡了,并不表示它所指的內存會被自動釋放。2)內存被釋放了,并不表示指針會消亡或者成了NULL 指針。 8。杜絕“野指針” “野指針”不是NULL 指針,是指向“垃圾”內存的指針。人們一般不會錯用NULL指針,因為用if 語句很容易判斷。但是“野指針”是很危險的,if 語句對它不起作用。 “野指針”的成因主要有兩種: (1)指針變量沒有被初始化,就直接使用。 任何指針變量剛被創建時不會自動成為NULL 指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要么將指針設置為NUL

16、L,要么讓它指向合法的內存。例如 char *p = NULL; char *str = (char * malloc(100; (2)指針p 被free 或者delete 之后,沒有置為NULL,讓人誤以為p 是個合法的指針。 (3)指針操作超越了變量的作用 范圍。 9.有了malloc/free 為什么還要new/delete ? malloc 與free 是C+/C 語言的標準庫函數,new/delete 是C+的運算符。我們在c+里面需要的是運算符,而不是函數,因為函數的功能不夠強大。它們都可用于申請動態內存和釋放內存。 對于非內部數據類型的對象而言,光用malloc/free 無法滿

17、足-動態對象-的要求;對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。這些都是定義對象的要求,不管是靜態對象還是動態對象都要滿足這兩個條件。現在我們需要定義一個動態對象,例如對象指針,定義一個指向對象的指針變量a,如Obj *a,那么a就是動態對象。假如此時沒有定義其他對象,那么怎么使這個動態對象a初始化呢,因為他不能使用構造函數和析構函數。由于malloc/free 是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加于malloc/free。因此C+語言需要一個能完成動態內存分配和初始化工作的運算符new,以及一個能完成清理與釋放內存

18、工作的運算符delete。注意new/delete 不是庫函數。我們發現利用new/delete運算符進行初始化和釋放內存,非常簡單,而用malloc/free則需要調用很多函數,根本無法滿足動態對象的要求。請看例子: 我們先看一看malloc/free 和new/delete 如何實現對象的動態內存管理: class Obj public : Obj(void cout << “Initialization” << endl; Obj(void cout << “Destroy” << endl; void Initialize(void co

19、ut << “Initialization” << endl; void Destroy(void cout << “Destroy” << endl; ; void UseMallocFree(void Obj *a = (obj *malloc(sizeof(obj; / 申請動態內存-不執行構造函數,需要通過下面的初始化語句才能進行初始化,非常麻煩。 a->Initialize(; / 初始化 / a->Destroy(; / 清除工作 free(a; / 釋放內存, -并不會執行析構函數,完成不了析構函數里面的最后的清理工作。

20、 void UseNewDelete(void Obj *a = new Obj; / 申請動態內存并且初始化, -并執行構造函數。 / delete a; / 清除并且釋放內存, -并執行析構函數。完成善后清理工作。 / 總結:malloc/free函數只是完成申請和釋放動態內存用的,是在c里面延續過來的,沒有其他功能。所以更不會執行構造函數和析構函數。所以無法滿足動態對象的要求。 new/delete運算符是在c+里面新增的,就是為了完成上面兩個函數不能完成的功能-動態對象的要求才產生的。這兩個運算符在執行的時候,受編譯器的控制,會自動執行構造函數和析構函數,就像創建一般對象的那 樣,自動

21、完成這些功能。這就是動態對象所要的功能。 / 注釋:類Obj 的函數Initialize 模擬了構造函數的功能,函數Destroy 模擬了析構函數的功能。函數UseMallocFree 中,由于malloc/free 不能執行構造函數與析構函數,必須調用成員函數 Initialize 和Destroy 來完成初始化與清除工作。函數UseNewDelete 則簡單得多。 所以我們不要企圖用malloc/free 來完成動態對象的內存管理,應該用new/delete。由于內部數據類型的“對象”沒有構造與析構的過程,對它們而言malloc/free 和new/delete 是等價的。既然new/de

22、lete 的功能完全覆蓋了malloc/free,為什么C+不把malloc/free 淘汰出局呢?這是因為C+程序經常要調用C 函數,而C 程序只能用malloc/free 管理動態內存。 如果用free 釋放“new 創建的動態對象”,那么該對象因無法執行析構函數而可能導致程序出錯。如果用delete 釋放“malloc 申請的動態內存”,理論上講程序不會出錯,但是該程序的可讀性很差。所以new/delete 必須配對使用,malloc/free 也一樣。 10.內存耗盡怎么辦? 如果在申請動態內存時找不到足夠大的內存塊,malloc 和new 將返回NULL 指針,宣告內存申請失敗。通常

23、有三種方式處理“內存耗盡”問題: (1)判斷指針是否為NULL,如果是則馬上用return 語句終止本函數。例如 void Func(void A *a = new A; if(a = NULL return; (2)判斷指針是否為NULL,如果是則馬上用exit(1終止整個程序的運行。例如: void Func(void A *a = new A; if(a = NULL cout << “Memory Exhausted” << endl; exit(1; (3)為new 和malloc 設置異常處理函數。例如Visual C+可以用_set_new_hander

24、函數為new 設置用戶自己定義的異常處理函數,也可以讓malloc 享用與new 相同的異常處理函數。 。 上述(1)(2)方式使用最普遍。如果一個函數內有多處需要申請動態內存,那么方式(1)就顯得力不從心(釋放內存很麻煩),應該用方式(2)來處理。 如果發生“內存耗盡”這樣的事情,一般說來應用程序已經無藥可救。如果不用exit(1 把壞程序殺死,它可能會害死操作系統。 11.。 malloc/free 的使用要點 函數malloc 的原型如下: void *malloc(size_t size; 用malloc 申請一塊長度為length 的整數類型的內存,程序如下:int *p = (in

25、t * malloc(sizeof(int * length;我們應當把注意力集中在兩個要素上:“類型轉換”和“sizeof”。 malloc 返回值的類型是void *,所以在調用malloc 時要顯式地進行類型轉換,將void* 轉換成所需要的指針類型。 malloc 函數本身并不識別要申請的內存是什么類型,它只關心內存的總字節數。 在malloc 的“(”中使用sizeof 運算符是 良好的風格,但要當心有時我們會昏了頭,寫出 p = malloc(sizeof(p這樣的程序來。 函數free 的原型如下: void free( void *memblock ; 為什么free 函數不象

26、malloc 函數那樣復雜呢?這是因為指針p 的類型以及它所指的內 存的容量事先都是知道的,語句free(p能正確地釋放內存。如果p 是NULL 指針,那么free 對p 無論操作多少次都不會出問題。如果p 不是NULL 指針,那么free 對p 連續操作兩次就 會導致程序運行錯誤。 12.new/delete 的使用要點 運算符new 使用起來要比函數malloc 簡單得多,直接定義指針變量賦值。例如: int *p1 = (int *malloc(sizeof(int * length; int *p2 = new intlength; 這是因為new 內置了sizeof、類型轉換和類型安全檢查功能。對于非內部數據類型的對象而言,new 在創建動態對象的同時完成了初始化工作。 如果對象有多個構造函數,那么new 的語句也可以有多種形式。例如: class Obj public : Obj(void; / 無參數的構造函數 Obj(int x; / 帶一個參數的構造函數 void Test(void Obj *a = new Obj; Obj *b = new Obj(1; / 初值為1 delete a; delete b; 如果用new 創建對象數組,那么只能使用對象的無參數構造函數:Obj *objects = new Ob

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論