內(nèi)存管理是C++最令人頭痛的問題蹲坷,也是C++最有爭議的地方驶乾。C++高手從中獲得了更好的性能,更大的自由循签,C++菜鳥獲取的則是一遍一遍的檢查代碼级乐。而這一切都源于C++內(nèi)存管理的靈活性,其多樣的內(nèi)存分配方式就是其靈活性的最好例證之一县匠。
一個程序要運行风科,就必須先將可執(zhí)行的程序加載到計算機內(nèi)存里,程序加載完畢后乞旦,會形成一個運行空間贼穆,并按照下圖所示進行布局。
- 代碼區(qū):存放的是程序的執(zhí)行代碼兰粉;
- 數(shù)據(jù)區(qū):存放的是全局數(shù)據(jù)故痊、常量、靜態(tài)變量等玖姑;
- 堆區(qū):存放的是動態(tài)內(nèi)存愕秫,供程序隨機申請使用;
- 棧區(qū):存放的是程序中所用到的局部數(shù)據(jù)焰络。
這些數(shù)據(jù)可以動態(tài)地反應程序中對函數(shù)的調(diào)用狀態(tài)戴甩,通過其軌跡也可以研究其函數(shù)機制。其中闪彼,除了代碼區(qū)不是我們能在代碼中直接控制的等恐,剩余三塊都是我們編碼過程中可以利用的。
在C++中,數(shù)據(jù)區(qū)又被分成自由存儲區(qū)课蔬、全局/靜態(tài)存儲區(qū)和常量存儲區(qū)囱稽,再加上堆區(qū)、棧區(qū)二跋,也就是說內(nèi)存被分成了5個區(qū)战惊。這5種不同的分區(qū)各有所長,適用于不同的情況扎即。
1.C++的內(nèi)存分配方式
1.1 棧區(qū)
在執(zhí)行函數(shù)時吞获,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元將自動被釋放谚鄙。棧內(nèi)存分配運算內(nèi)置于處理器的指令集中各拷,效率很高,但是所分配的內(nèi)存容量有限闷营。
1.2 堆區(qū)
堆是操作系統(tǒng)中的術語烤黍,是操作系統(tǒng)所維護的一塊特殊內(nèi)存,用于程序的內(nèi)存動態(tài)分配傻盟,C語言使用malloc從堆上分配內(nèi)存速蕊,使用free釋放已分配的對應內(nèi)存。
1.3 自由存儲區(qū)
自由存儲區(qū)是C++基于new操作符的一個抽象概念娘赴,凡是通過new操作符進行內(nèi)存申請规哲,該內(nèi)存即為自由存儲區(qū)。
那么自由存儲區(qū)是否能夠是堆(問題等價于new是否能在堆上動態(tài)分配內(nèi)存)诽表,這取決于operator new 的實現(xiàn)細節(jié)唉锌。自由存儲區(qū)不但可以是堆,還可以是靜態(tài)存儲區(qū)竿奏,這要看operator new在哪里為對象分配內(nèi)存糊秆。具體可以參考C++ 自由存儲區(qū)是否等價于堆?這篇文章议双。
1.4 全局/靜態(tài)存儲區(qū)
全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中痘番,在以前的C語言中,全局變量又分為初始化的和未初始化的平痰,在C++里面沒有作此區(qū)分汞舱,它們共同占用同一塊內(nèi)存區(qū)。
1.5 常量存儲區(qū)
比較特殊宗雇,里面存放的是常量昂芜,不允許修改。
2.堆與棧的區(qū)別
上述5種分區(qū)中赔蒲,最常用的就是堆和棧了泌神,最容易混淆的也是它們良漱。所以搞清楚堆和棧的區(qū)別是很有必要的。它們的區(qū)別主要有以下幾個方面
2.1 管理方式
棧是由編譯器自動管理的欢际,無須手工控制母市;堆的釋放工作由程序員控制,容易產(chǎn)生內(nèi)存泄漏损趋。
2.2 空間大小
一般來說在32位系統(tǒng)下患久,堆內(nèi)存可以達到4GB(2^32B = 4 * 2^30B = 4GB)的空間,從這個角度來看堆內(nèi)存幾乎是沒有什么限制的浑槽。但是對于棧來講蒋失,一般都是有一定空間大小的。例如桐玻,VS2017默認椄萃欤空間大小是1M。
2.3 碎片問題
對堆來講镊靴,頻繁的new/delete會造成內(nèi)存空間的不連續(xù)铣卡,從而產(chǎn)生大量的碎片,使程序效率降低邑闲。對于棧來講,則不存在這個問題梧油,因為棧是隊列苫耸,其中的數(shù)據(jù)必須遵循先進后出的規(guī)則,相互之間緊密排列儡陨,絕不會留給其他數(shù)據(jù)可插入之空隙褪子,所以永遠都不可能有一個內(nèi)存塊從棧中間彈出。
2.4 生長方向
對于堆來講骗村,其生長方向是向上的嫌褪,也就是向著內(nèi)存地址增加的方向增長;對于棧來講胚股,它的生長方向是向下的笼痛,是向著內(nèi)存地址減小的方向增長的。
2.5 分配方式
堆都是動態(tài)分配的琅拌,沒有靜態(tài)分配的堆缨伊。棧有兩種分配方式:靜態(tài)分配和動態(tài)分配。靜態(tài)分配是編譯器完成的进宝,比如局部變量的分配刻坊。動態(tài)分配由alloca函數(shù)完成,但是棧的動態(tài)分配和堆是不同的党晋,它的動態(tài)分配是由編譯器進行釋放的谭胚,無須我們手工實現(xiàn)徐块。
2.6 分配效率
棧是系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計算機會在底層對棧提供支持:它會分配專門的寄存器存放棧的地址灾而,而且壓棧出棧都會有專門的指令來執(zhí)行胡控,這就決定了棧的效率比較高。堆則是C/C++函數(shù)庫提供的绰疤,它的機制很復雜铜犬,例如為了分配一塊內(nèi)存,庫函數(shù)會按照一定的算法(具體的算法可以參考數(shù)據(jù)結(jié)構(gòu)/操作系統(tǒng))在堆內(nèi)存中搜索可用的足夠大小的空間轻庆,如果沒有足夠大小的空間(可能是由于內(nèi)存碎片太多)癣猾,則可能調(diào)用系統(tǒng)功能去增加程序數(shù)據(jù)段的內(nèi)存空間,這樣就有機會分到足夠大小的內(nèi)存了余爆,然后返回纷宇。顯然,堆的效率比棧要低得多蛾方。
2.7 總結(jié)
堆和棧相比像捶,由于堆使用了大量new/delete,容易造成大量的內(nèi)存碎片桩砰,而且它沒有專門的系統(tǒng)支持拓春,效率很低,另外它還可能引發(fā)用戶態(tài)和核心態(tài)的切換亚隅,以及內(nèi)存的申請硼莽,代價會變得很高。所以棧在程序中是應用最廣泛的煮纵,就算是函數(shù)的調(diào)用也會利用棧去完成懂鸵,函數(shù)調(diào)用過程中的參數(shù)、返回的地址行疏、EBP和局部變量都是采用棧的方式存放的匆光。所以,推薦大家盡量多用棧酿联,而不是用堆终息。
雖然棧有如此多的好處,但是由于和堆相比它不是那么靈活贞让,有時候會分配大量的內(nèi)存空間采幌,在遇到這種情況時還是用堆好一些。
3.常見的內(nèi)存處理規(guī)則
【規(guī)則1】用malloc或new申請內(nèi)存之后震桶,應該立即檢查指針值是否為NULL休傍。防止使用指針值為NULL的內(nèi)存。
【規(guī)則2】不要忘記為數(shù)組和動態(tài)內(nèi)存賦初值蹲姐。防止將未被初始化的內(nèi)存作為右值使用磨取。
【規(guī)則3】避免數(shù)組或指針的下標越界人柿,特別要當心發(fā)生“多1”或者“少1”操作。
【規(guī)則4】動態(tài)內(nèi)存的申請與釋放必須配對忙厌,防止內(nèi)存泄漏凫岖。
【規(guī)則5】用free或delete釋放了內(nèi)存之后,立即將指針設置為NULL逢净,防止產(chǎn)生“野指針”哥放。
個人主頁: