火善棟 楊旭東
(1.重慶三峽學院,重慶萬州 404100;2.重慶安全技術(shù)職業(yè)學院,重慶萬州 404120)
對于C/C++程序設(shè)計的內(nèi)存管理,一般而言,我們可以簡單地把內(nèi)存分為三個部分:靜態(tài)區(qū)、棧和堆.但是,很多初學者甚至是一些所謂的老手由于沒有從本質(zhì)上正確理解堆和棧之間的區(qū)別,經(jīng)常把堆和?;鞛橐粓F,甚至認為堆和棧是同一個概念,這也是他們對C/C++編程中出現(xiàn)的某些錯誤或者某些結(jié)果百思不得其解的一個重要原因.其實,堆棧就是棧,而不是堆,堆和棧是兩個完全不同的概念,堆和棧都有著各自不同的特征.正確理解這三者尤其是堆和棧在內(nèi)存中的分布特點,對于正確編寫高質(zhì)量的C/C++程序有著至關(guān)重要的作用,本文借助匯編語言低級化的特點,試圖通過一個簡單的C++小程序從底層對其在內(nèi)存中的分布情況及其特征作了比較詳細的分析和探討,以幫助讀者正確理解這三者之間的聯(lián)系和區(qū)別.
下面就通過一個簡單的C++小程序來詳細說明靜態(tài)內(nèi)存、棧內(nèi)存和堆內(nèi)存的內(nèi)在區(qū)別和聯(lián)系,該小程序定義了一個全局變量dd、一個靜態(tài)的局部變量aa和一個用指針p定義的字符串常量(對應于靜態(tài)內(nèi)存)、一個普通數(shù)組bb[](對應于棧內(nèi)存)和一個動態(tài)數(shù)組new int[3](對應于堆內(nèi)存),然后分別對其作一些簡單的操作,并在vc6.0環(huán)境下通過反匯編代碼以展示其在內(nèi)存中的分布情況,表1為該c++源程序和反匯編代碼對照表.從表1中可以看出,static變量aa和全局變量dd、用指針p定義的字符串常量、普通數(shù)組bb[]和動態(tài)數(shù)組new int[3]并不是分布在同一塊內(nèi)存區(qū)域中.我們把static變量和用指針定義的字符串常量所分布的區(qū)域成為靜態(tài)區(qū),動態(tài)數(shù)組也就是用new操作符或malloc系列函數(shù)動態(tài)申請到的內(nèi)存區(qū)域稱為堆區(qū),普通數(shù)組等其它普通局部變量等所分布的區(qū)域稱為棧區(qū).
當某一個函數(shù)要操作靜態(tài)區(qū)中的數(shù)據(jù)時,對于 static變量或全局變量則直接通過調(diào)用其所在的內(nèi)存地址對其操作,如示例中9和10代碼片段所示所示,對于字符串常量則通過調(diào)用其所在的內(nèi)存塊的首地址(該首地址保存在調(diào)用函數(shù)的棧中)對其操作,如代碼片段8所示;對于普通數(shù)組和普通的局部變量則直接保存在調(diào)用函數(shù)的棧中并對其進行操作,其過程最為簡單和高效,如代碼片段11和代碼片段8、13和17部分指令所示;對于動態(tài)內(nèi)存,則先要調(diào)用一系列相關(guān)的申請動態(tài)內(nèi)存的函數(shù)(大部分反匯編代碼省略),并將所申請到的動態(tài)內(nèi)存的首地址通過 EAX返回保存到調(diào)用函數(shù)的棧中,然后通過這個首地址對堆中的內(nèi)存和數(shù)據(jù)進行操作,如示例中代碼片段13、14、15、16所示,由此可見,這一過程最有復雜但更加靈活.
表1 C++源代碼和反匯編代碼對照表
圖1為示例中各個變量及其所在的內(nèi)存區(qū)域分布結(jié)構(gòu)示意圖(所有內(nèi)存區(qū)域從上到下依次增大).
說明:從圖1中可以看出,從申請動態(tài)內(nèi)存到最后釋放動態(tài)內(nèi)存,在棧中有三個dword字段同時存放著堆內(nèi)存的首地址,看似不合理,實則這是由反匯編代碼內(nèi)部執(zhí)行機制所造成的.
從圖1中可以看出,棧內(nèi)存數(shù)據(jù)的存放是以ebp指針為基準向著地址減小的方向存放的,當然,從表一中反匯編代碼也可以看出,對棧內(nèi)數(shù)據(jù)的操作也是以ebp為基準的;堆區(qū)是以堆內(nèi)存的首地址為基準向著地址增大的方向存放的;靜態(tài)區(qū)中 static變量是以第一個 static變量(或全局變量)開始向著地址增大的方向存放,而字符串常量則是存放在靜態(tài)區(qū)中另一塊區(qū)域,它與 static變量并不是連續(xù)存放的;字符串常量和堆區(qū)的首地址通過相關(guān)的局部變量保存在調(diào)用函數(shù)的堆棧中.
從以上分析和說明,可以驗證和總結(jié)如下結(jié)論:
靜態(tài)區(qū)、棧和堆分別屬于計算機內(nèi)存中三塊不同的區(qū)域,靜態(tài)區(qū)中的 static變量和全局變量由編譯器在編譯的時候分配(這從示例代碼的反匯編代碼7和8可以反應出來),它與棧并沒有直接的聯(lián)系,這也是靜態(tài)區(qū)的內(nèi)容在整個程序的生命周期內(nèi)一直能夠存在根本原因,靜態(tài)區(qū)的字符串常量將其首地址保存在調(diào)用函數(shù)的堆棧中,調(diào)用函數(shù)通過這個首地址對該字符串進行操作.
棧保存局部變量(static局部變量除外),對棧內(nèi)數(shù)據(jù)的操作和訪問是通過ebp指針來進行的.棧上的內(nèi)容只在函數(shù)的范圍內(nèi)存在,當函數(shù)運行結(jié)束時,棧內(nèi)存會自動釋放[2],其特點是簡單執(zhí)行效率高,這也是函數(shù)不能直接返回指向棧內(nèi)存內(nèi)容指針的根本原因;棧內(nèi)的數(shù)據(jù)是按內(nèi)存地址從大到小的順序進行存放的.
堆是由malloc系列函數(shù)或new操作符分配到的內(nèi)存,其首地址通過 EAX寄存器保存在操作該堆內(nèi)存的函數(shù)的棧中,調(diào)用函數(shù)通過該首地址對堆內(nèi)存進行操作,其過程比較復雜;棧內(nèi)存必須通過free或delete函數(shù)進行釋放,否則,其數(shù)據(jù)在整個程序的運行過程中一直有效[6];堆中的數(shù)據(jù)是按內(nèi)存地址從小到大的順序進行存放的.
[1]譚文,等.天書夜讀:從匯編語言到 Windows內(nèi)核編程[M].北京:電子工業(yè)出版社,2008.
[2]火善棟.通過匯編語言理解函數(shù)調(diào)用的內(nèi)在機理[J].計算機時代,2010(7).
[3]呂鳳翥.C++語言程序設(shè)計[M].北京:清華大學出版社,2003.
[4]卜艷萍.匯編語言程序設(shè)計教程[M].北京:清華大學出版社,2008.
[5]王曉東.C++程序設(shè)計簡明教程[M].北京:中國水利水電出版社,2008.
[6]林銳.高質(zhì)量程序設(shè)計指南——C++/C語言[M].北京:電子工業(yè)出版社,2003.