張會
摘 要: 詳細闡述了C++編譯器的內存分配形式,給出了堆、棧、文字常量區(qū)、寄存器區(qū)、靜態(tài)區(qū)、程序代碼區(qū)的分配策略,分析了內存分配中易產生的問題及導致程序運行出錯的原因和解決辦法,從而避免程序異常和內存錯誤,保證程序的健壯性和正確性。
關鍵詞: 內存; 堆; 棧; C++語言
中圖分類號:TP312 文獻標志碼:A 文章編號:1006-8228(2014)05-44-03
Abstract: The memory allocation strategy C++ compiler is described in detail in this paper. The distribution strategy of heap, stack, literals memory, register memory in C++ language is given. The causes and the solution of memory allocation problems and program running error are analyzed to avoid exception and memory errors, and guarantee the correctness and robustness of program.
Key words: memory; heap; stack; C++ language
0 引言
C++編譯器根據(jù)數(shù)據(jù)在內存中的生存期不同,將用戶使用的內存分為程序區(qū)、靜態(tài)存儲區(qū)和動態(tài)存儲區(qū)三個區(qū)域,其中動態(tài)存儲區(qū)又分為堆區(qū)、棧區(qū)和寄存器區(qū)。
1 內存分配形式
C++中內存分配形式有以下六種。
1.1 棧區(qū)(stack)
棧由編譯器自動分配及釋放,用于存放函數(shù)參數(shù)值,局部變量值等。棧是一塊連續(xù)的內存區(qū)域,它的大小為2M固定常數(shù),因此程序中的變量能從棧中獲取空間較少。若棧的剩余空間大于所申請空間,編譯器將為程序提供??臻g,且按照向低地址生長方向分配連續(xù)的內存空間;若申請的空間超過棧的剩余空間,將報異常,提示棧溢出(overflow)[1]。
在調用函數(shù)時,第一個進棧的是被調用函數(shù)下一行的內存地址,再是函數(shù)參數(shù),參數(shù)入棧的順序自右向左,再是函數(shù)的局部變量。函數(shù)調用結束后,首先出棧的是被調函數(shù)中的局部變量,再是參數(shù),次序是自左向右,所有變量和參數(shù)都出棧后,棧頂指針指到調用函數(shù)的下一行內存地址,程序根據(jù)該地址跳轉到函數(shù)調用處的下一行自動執(zhí)行。入棧數(shù)據(jù)的內存地址隨著入棧順序的先后向著內存地址減小的方向增長,隨著數(shù)據(jù)不斷入棧,內存地址不斷變小。由于棧的先進后出原則,棧不會產生內存碎片。雖然棧內存小,但效率高,棧中存儲的數(shù)據(jù)只在函數(shù)內有效,函數(shù)調用結束會因為數(shù)據(jù)出棧而被釋放。
1.2 堆區(qū)(heap)
堆內存由程序員分配及釋放,若程序員在程序中未釋放,則在程序運行結束后,由操作系統(tǒng)回收。堆是不連續(xù)的空閑內存區(qū)域,各塊區(qū)域由鏈表連接起來,其內存大小由系統(tǒng)中虛擬內存來確定,因此其空間較大,可以存放大量數(shù)據(jù)。
堆區(qū)分配內存空間時,系統(tǒng)會遍歷用于記錄內存空閑塊的鏈表,首次找到一個空間大于所申請空間的堆結點時,將該結點從鏈表中刪除。并將該結點的內存分配給程序,同時在這塊內存區(qū)域首地址處記錄本次內存分配大小,程序員在用delete或free釋放內存時,以此識別要刪除內存大小并正確刪除該段內存。若申請的內存空間與堆結點上的內存空間不相等,則系統(tǒng)會自動將堆結點上多余內存空間回收到空閑鏈表中。堆區(qū)在分配內存時,鏈表中地址遍歷方向是由低向高,因此堆區(qū)分配內存時是按照向高地址生長的方向分配不連續(xù)內存空間[1]。堆內存分配是由程序員進行分配及釋放,速度較慢,易產生內存碎片。
堆是不連續(xù)的內存區(qū)域,由鏈表將其串接起來的空閑塊,其不能像棧一樣可以為其中的某個存儲單元命名,堆中的每個內存單元都是匿名的,對堆的訪問只能先在堆中申請內存,再把申請內存的首地址保存在一個指針中,再通過指針來訪問內存。在C++中用malloc()和new關鍵字申請堆內存。寄存器區(qū)一般用于保存棧頂指針和指令指針。
1.4 靜態(tài)區(qū)(static)
全局變量和靜態(tài)變量的存儲是放在一塊的,已初始化的全局變量和靜態(tài)變量放在一塊區(qū)域,未初始化的全局變量和靜態(tài)變量存放于相鄰的另一塊區(qū)域,內存在程序結束后由系統(tǒng)釋放。靜態(tài)變量的空間在程序編譯階段進行分配,所分配內存在程序整個運行期間都存在[2]。
1.6 程序代碼區(qū)
存放函數(shù)體的二進制代碼。
2 內存分配中若干問題的分析
如果對內存分配策略理解不清楚,且程序設計不當,就極易引起對運行結果異常,且難以捕獲由內存分配所引起的錯誤。
本程序的輸出結果既不是隨機值,也不是3而是4,是由棧空間的連續(xù)分配所引起。在main()函數(shù)中,首先調用fun()函數(shù),此時fun1()代碼行地址入棧,接著fun()中的局部變量x入棧,fun()調用結束后把x的內存地址值返回給main()函數(shù)中的p指針,此時棧中的x已經出棧。接著在main()中調用fun1()函數(shù),此時對fun1()中的a進行棧內存的分配,其分配的??臻g正好是fun()函數(shù)中x變量所釋放的內存,對該內存賦值4后,回到主函數(shù),此時??臻g的a變量內存被釋放,而此時p指針從未改變,與此同時,程序沒有其他語句代碼或變量需要分配棧空間,因此p指針所指的??臻g的值未被覆蓋,保留最后一次所賦值4,故程序最后輸出的值是4。因此掌握了內存中存儲空間的分配情況及原理,不難分析其異常的運行結果。
為防止讓指向常量的指針對所指常量進行值的改變,解決的辦法是把p聲明成常量指針,如:const char *p;以保證不能改變所指常量的值,若試圖通過指針改變常量的值,在編譯檢查時將報錯,不致于如上述程序段發(fā)生運行時錯誤。
2.3 堆區(qū)內存分配
堆中的內存是匿名的,對堆內存進行訪問只能通過指針進行訪問。通過指針訪問堆內存時,一定要注意防止內存泄露。內存泄露是指程序從堆中分配的內存塊該內存釋放后即存放了其他數(shù)據(jù),但p中的地址值仍然是所釋放那段內存的地址值,因此第5行輸出*p的值是所釋放那段內存中已存放的隨機值。第6行p1指向新申請的一段內存,由于編譯器會默認將釋放掉的內存空間回收然后分配給新開辟的空間,因此第6行p1其實指向的是剛通p所釋放掉的空間3 內存分配中存在的其他情況
內存分配發(fā)生的異常,編譯器不能通過語法檢查發(fā)現(xiàn),通常會發(fā)生程序運行結果異常,該類錯誤不易被捕捉,從而給程序員檢查程序錯誤帶來不便,因此在寫程序時盡量避免內存分配錯誤。常見的內存操作異常如下。
⑴ 內存分配失敗卻直接使用,如返回一個NULL指針。
⑵ 訪問內存時超出了內存分配的邊界。越界的內存可能保存其他變量的值,訪問該內存變量的值,可能產生異常,導致程序的終止甚至崩潰[4]。
⑶ 動態(tài)申請了內存空間如鏈表,而未動態(tài)釋放內存空間,或未完全釋放,只釋放了鏈表中的表頭指針所指向結點,而未釋放鏈表中的各個結點的內存空間,從而造成內存泄露。
(4)訪問已經釋放的內存,釋放已經釋放的內存。從而導致程序無法正確運行,得到無效值。
4 結束語
本文闡述了C++編譯器的內存分配形式,提出了堆、棧、文字常量區(qū),寄存器區(qū)的分配策略,分析了內存分配中易產生的問題和原因,同時給出了因內存分配而導致程序錯誤的解決辦法,總之要解決程序中因內存分配所產生的問題,其前提是必須要清楚內存分配策略。通過本文對C++中內存分配策略的研究,可以使讀者在以后的程序編寫中有效的避免內存分配錯誤,從而保證程序的健壯性和正確性。
參考文獻:
[1] 王文龍.C/C++數(shù)據(jù)內存分配和指針使用中若干問題的分析[J].喀什
師范學院學報,2013.34(6):36-37
[2] 韓雨澇.C語言動態(tài)內存分配研究及應用[J].計算機時代,2009.5:
33-34
[3] 王金玲,柴萬東.C++動態(tài)內存分配研究[J].赤峰學院學報,2009.25
(4):19-20
[4] Eri R.Hanly.C語言詳解[M].人民郵電出版社,2007.