本節(jié)是操作系統(tǒng)系列教程的第三篇文章,屬于操作系統(tǒng)第一章即基礎篇,在真正開始操作系統(tǒng)相關章節(jié)前在這一部分回顧一些重要的主題,算是溫故知新吧,以下是目錄,由于本文篇幅較多因此接下來會分三次發(fā)布,目錄中黑體為本篇內(nèi)容。
什么是內(nèi)存
C/C++內(nèi)存模型
堆區(qū)與棧區(qū)的本質(zhì)
Java內(nèi)存模型
Jave中的堆區(qū)與棧區(qū)是如何實現(xiàn)的
Python內(nèi)存模型
指針與引用
進程的內(nèi)存模型
幻想大師-操作系統(tǒng)
總結(jié)
什么是內(nèi)存
0和1這兩個簡單的數(shù)字能做什么?在其它學科中也許什么都做不了,但是在計算機科學中這就是全部。精彩紛呈的計算機世界正是構(gòu)筑在這樣兩個簡單數(shù)字之上。
內(nèi)存本身其實非常簡單,內(nèi)存的作用就是用來裝數(shù)字0和數(shù)字1的,如圖所示,圖中的一個盒子就是內(nèi)存的一個基本單元,裝的不是0就是裝的1。
內(nèi)存由一大堆的“盒子”組成,每個盒子中要么是0要么是1,其中8個盒子被稱之為一個“字節(jié)”,每8個盒子也就是一個字節(jié)都有一個編號,這些編號就是簡單的從0開始依次累加的,這個編號就被稱之為“ 內(nèi)存地址 ”。其中左邊的數(shù)字是內(nèi)存地址,每一排是一個字節(jié),圖中展示的就是一個8字節(jié)大小的內(nèi)存。
而對于我們平時使用的比如2G、4G甚至8G大小的內(nèi)存來說,只不過就是“盒子”多一點能裝的01多一點而已,本質(zhì)上和我們在這里展示的8字節(jié)大小的內(nèi)存沒有任何區(qū)別。
在后面的章節(jié)中我將用右圖來表示內(nèi)存,但是你的大腦里一定要有左圖這樣一個概念。當計算機在執(zhí)行我們的程序時,無論是我們的機器指令還是機器指令操作的數(shù)據(jù),都需要存放在這些小盒子中(內(nèi)存)。
以上就是從硬件角度來看內(nèi)存,那么從編程語言上來看,程序員應該如何理解內(nèi)存呢?
C/C++內(nèi)存模型
對于C/C++程序員來說,常用的int,char等變量都被裝在盒子中,char值只需要一排盒子就能裝下(8bit),一個int值一般需要四排盒子才能裝得下。連續(xù)幾排裝有同樣類型變量的盒子就是數(shù)組(array),連續(xù)幾排裝有不同類型變量的盒子就是結(jié)構(gòu)體(struct),C/C++語言中不管多么復雜的數(shù)據(jù)結(jié)構(gòu)都是在此基礎上構(gòu)建出來的,都需要裝在這些盒子里,沒什么大不了的。
現(xiàn)在你已經(jīng)知道了對于C/C++程序員來說,我們使用的變量是直接放在內(nèi)存中的(盒子), 每一排盒子的地址就是我們熟知的“指針” ,請記住,指針就是你使用的變量在內(nèi)存中的地址,僅此而已。
C/C++程序在被執(zhí)行時,需要在內(nèi)存中劃出兩段區(qū)域用于存放數(shù)據(jù),這兩個區(qū)域就是我們熟悉的堆(Heap)和棧(Stack),也稱堆區(qū)和棧區(qū),如圖所示,其中數(shù)據(jù)段和代碼段我們已經(jīng)熟悉了,在這里我們將進一步完善C/C++程序在內(nèi)存中的樣子,如圖所示,其中堆區(qū)緊鄰數(shù)據(jù)段,在數(shù)據(jù)段之上,而棧在最上方,棧和堆之間是尚未被使用的內(nèi)存,隨著程序的運行,當程序申請內(nèi)存時棧區(qū)和堆區(qū)之間的空隙會減小,當程序釋放內(nèi)存后空隙會擴大,這就是C/C++程序的內(nèi)存模型。
每個函數(shù)運行時都會在棧區(qū)上占用一塊內(nèi)存,這塊內(nèi)存中保存的是調(diào)用函數(shù)的參數(shù)以及函數(shù)中的定義的局部變量,這些變量在函數(shù)調(diào)用完成后會被釋放。從這里可以看出棧上的變量無需程序員關心其釋放問題,當函數(shù)調(diào)用完畢后會自動釋放所占用的空間。
和棧上的變量不同的是,堆上分配的內(nèi)存不會像棧一樣被自動釋放,在堆上分配的內(nèi)存需要程序員手動釋放,如果程序員在堆上分配了一塊內(nèi)存,但在使用完后忘記釋放,這種情況就被稱之為“內(nèi)存泄漏”,所謂“內(nèi)存泄漏”就是使用完畢后的內(nèi)存沒有釋放掉,但是這塊內(nèi)存也不能被用作其它地方從而導致堆占用的內(nèi)存不斷增大,表現(xiàn)出來的就是如果我們?nèi)?a target="_blank">檢測程序所占用的內(nèi)存,會發(fā)現(xiàn)程序所占用的內(nèi)存不斷增大,當操作系統(tǒng)是不可能坐視某個進程不斷吞噬掉系統(tǒng)內(nèi)存的,當出現(xiàn)系統(tǒng)內(nèi)存資源不足時將觸發(fā)操作系統(tǒng)的保護機制,這在Linux中就是著名的OOM Killer,即Out Of Memory Killer,OOM Killer會根據(jù)一些策略Killer有問題的進程,這個進程通常都是占用內(nèi)存最多的那個。
下面我們用一小段C代碼來實際演示一變量是如何在堆區(qū)棧區(qū)上分配的,不用擔心,這段代碼非常簡單:
include
如圖所示,這就是以上代碼運行過程中的樣子,你會發(fā)現(xiàn),每個函數(shù)在被執(zhí)行的時候都在棧區(qū)上占有一小段,在這一小段中存放當前函數(shù)中定義的局部變量和傳入函數(shù)的參數(shù)。每個函數(shù)所占用的這一段內(nèi)存有一個很形象的名字,叫做“棧幀(stack frame)”,原因就在于棧是隨著函數(shù)調(diào)用一幀一幀增加的,每個函數(shù)在被調(diào)用時都會在棧上分配一幀,所以就叫棧幀。這個詞請大家不必去深究,每個被調(diào)函數(shù)在棧區(qū)上做占用的內(nèi)存總要有個名字,棧幀只不過比較形象而已。
這段代碼中,main函數(shù)會調(diào)用函數(shù)f1,f1會調(diào)用函數(shù)f2(),其中變量a,b,c以及heap依次被放在各自函數(shù)的棧幀中,值得注意的一點在于, heap這個變量本身是在棧上的,但是heap所指向的內(nèi)存是分配在堆上的 ,heap本身僅僅保存的是4這個值在內(nèi)存中的 位置 ,比如這里的0x10,表示的就是4這個值放在了內(nèi)存0x10的這個位置上,heap就是C/C++語言中所謂的指針。
你會發(fā)現(xiàn)隨著函數(shù)的調(diào)用,棧是不斷在擴大的,當f2,f1執(zhí)行完畢返回main時就是如下圖所示的樣子。
從圖中我們可以看出,f2在執(zhí)行完畢后,f2所占用的內(nèi)存就被回收了,所謂“回收”就是這塊內(nèi)存又可以用作其它用途了。f1執(zhí)行完畢后所占用的內(nèi)存同樣也被回收,這樣我們就又回到了main()函數(shù)中。
這個過程中我們還會發(fā)現(xiàn)一個很有意思的現(xiàn)象就是最先被使用的棧幀其實是最后才被釋放的,這種先進后出的性質(zhì)就被稱之為“棧”,如下圖所示。所以你會看到“?!斑@個詞更多的是指順序上的先進后出,只不過函數(shù)調(diào)用時所占用的內(nèi)存在使用方式上也是先進后出的,所以這塊內(nèi)存就被稱之為棧區(qū)了。
在講解完棧之后,我們來看看堆,不同于像a,b,c這樣存在于棧區(qū)上的變量,棧區(qū)上的變量可以在函數(shù)執(zhí)行完成后被自動釋放掉,在堆區(qū)上的分配內(nèi)存除非程序員手動調(diào)用free,delete明確的告知內(nèi)存使用完畢,否則這塊內(nèi)存就會一直被占用而不能用作其它用途,這就是堆區(qū)。
你可能會問,什么樣的變量在需要在堆上分配呢,我們知道,函數(shù)調(diào)用完成后棧上的分配的局部變量會因為棧幀被釋放而不再可用,堆區(qū)的存在就是為了解決這個問題,堆區(qū)中申請的內(nèi)存不會因為棧幀的釋放而不再可用,使得變量的生命周期不再局限于某個函數(shù),其生命周期是靠程序員用malloc(new)以及free(delete)來控制的,這樣的變量在使用時可以跨越函數(shù)調(diào)用。
另外一點值得注意的是,f2函數(shù)中我們在堆上申請了一塊內(nèi)存用來存放整數(shù),但是f2執(zhí)行完成后并沒有去釋放這塊內(nèi)存,根據(jù)堆的性質(zhì)我們知道這塊函數(shù)在接下來的運行過程中無法再被使用,就好像這塊內(nèi)存被遺忘了一樣,這就是內(nèi)存泄漏。
-
內(nèi)存
+關注
關注
8文章
3040瀏覽量
74172 -
操作系統(tǒng)
+關注
關注
37文章
6859瀏覽量
123501 -
C++
+關注
關注
22文章
2113瀏覽量
73742
發(fā)布評論請先 登錄
相關推薦
評論