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