C 程式記憶體結構

2021-07-15 05:32:57 字數 3737 閱讀 3359

標籤(空格分隔): c++ 記憶體

棧區(stack):又編譯器自動分配釋放,存放函式的引數值,區域性變數的值等,其操作方式類似於資料結構的棧。

堆區(heap):一般是由程式設計師分配釋放,若程式設計師不釋放的話,程式結束時可能由os**,值得注意的是他與資料結構的堆是兩回事,分配方式倒是類似於資料結構的鍊錶。

未初始化資料區(bss):未初始化或初值為0的全域性變數和靜態區域性變數。

資料區(data segment):已初始化且初值非0的全域性變數和靜態區域性變數

程式**區:可執行**、字串字面值、唯讀變數二進位制**。

下圖便是linux系統乙個程序的記憶體占用示意圖:

1.從低位址到高位址分別為:**段、(初始化)資料段、(未初始化)資料段(bss)、堆、棧、命令列引數和環境變數

2.堆向高記憶體位址生長  

3.棧向低記憶體位址生長

其中,使用者位址空間中的藍色條帶對應於對映到物理記憶體的不同記憶體段,灰白區域表示未對映的部分。這些段只是簡單的記憶體位址範圍,與intel處理器的段沒有關係。

上圖中random stack offset和random mmap offset等隨機值意在防止惡意程式。linux通過對棧、記憶體對映段、堆的起始位址加上隨機偏移量來打亂布局,以免惡意程式通過計算訪問棧、庫函式等 位址。execve(2)負責為程序**段和資料段建立對映,真正將**段和資料段的內容讀入記憶體是由系統的缺頁異常處理程式按需完成的。另 外,execve(2)還會將bss段清零。

1 核心空間

核心總是駐留在記憶體中,是作業系統的一部分。核心空間為核心保留,不允許應用程式讀寫該區域的內容或直接呼叫核心**定義的函式。
2 棧(stack)

棧又稱堆疊,由編譯器自動分配釋放,行為類似資料結構中的棧(先進後出)。堆疊主要有三個用途:

為函式內部宣告的非靜態區域性變數(c語言中稱「自動變數」)提供儲存空間。記錄函式呼叫過程相關的維護性資訊,稱為棧幀(stack frame)或過程活動記錄(procedure activation record)。它包括函式返回位址,不適合裝入暫存器的函式引數及一些暫存器值的儲存。除遞迴呼叫外,堆疊並非必需。因為編譯時可獲知區域性變數,引數和返回位址所需空間,並將其分配於bss段。

臨時儲存區,用於暫存長算術表示式部分計算結果或alloca()函式分配的棧內記憶體。

持續地重用棧空間有助於使活躍的棧記憶體保持在cpu快取中,從而加速訪問。程序中的每個執行緒都有屬於自己的棧。向棧中不斷壓入資料時,若超出其容量就會 耗盡棧對應的記憶體區域。這將觸發乙個頁錯誤。此時若棧的大小低於堆疊最大值rlimit_stack(通常是8m),則棧會動態增長,程式繼續執行。對映 的棧區擴充套件到所需大小後,不會再收縮回去。

linux中ulimit -s命令可檢視和設定堆疊最大值,當程式使用的堆疊超過該值時, 發生棧溢位(stack overflow),程式收到乙個段錯誤(segmentation fault)。注意,調高堆疊容量可能會增加記憶體開銷和啟動時間。

堆疊的大小在執行時由核心動態調整。

3 記憶體對映段(mmap)

該區域用於對映可執行檔案用到的動態鏈結庫。在linux 2.4版本中,若可執行檔案依賴共享庫,則系統會為這些動態庫在從0x40000000開始的位址分配相應空間,並在程式裝載時將其載入到該空間。在 linux 2.6核心中,共享庫的起始位址被往上移動至更靠近棧區的位置。

從程序位址空間的布局可以看到,在有共享庫的情況下,留給堆的可用空間還有兩處:一處是從.bss段到0x40000000,約不到1gb的空間;另一處是從共享庫到棧之間的空間,約不到2gb。這兩塊空間大小取決於棧、共享庫的大小和數量。這樣來看,是否應用程式可申請的最大堆空間只有2gb?事實 上,這與linux核心版本有關。在上面給出的程序位址空間經典布局圖中,共享庫的裝載位址為0x40000000,這實際上是linux kernel 2.6版本之前的情況了,在2.6版本裡,共享庫的裝載位址已經被挪到靠近棧的位置,即位於0xbf******附近,因此,此時的堆範圍就不會被共享庫 分割成2個「碎片」,故kernel 2.6的32位linux系統中,malloc申請的最大記憶體理論值在2.9gb左右。

4 堆(heap)

堆用於存放程序執行時動態分配的記憶體段,可動態擴張或縮減。堆中內容是匿名的,不能按名字直接訪問,只能通過指標間接訪問。當程序呼叫malloc(c)/new(c++)等函式分配記憶體時,新分配的記憶體動態新增到堆上(擴張);當呼叫free(c)/delete(c++)等函式釋放 記憶體時,被釋放的記憶體從堆中剔除(縮減) 。

分配的堆記憶體是經過位元組對齊的空間,以適合原子操作。堆管理器通過鍊錶管理每個申請的記憶體,由於堆申請和釋放是無序的,最終會產生記憶體碎片。堆記憶體一般由應用程式分配釋放,**的記憶體可供重新使用。若程式設計師不釋放,程式結束時作業系統可能會自動**。

堆的末端由break指標標識,當堆管理器需要更多記憶體時,可通過系統呼叫brk()和sbrk()來移動break指標以擴張堆,一般由系統自動呼叫。

使用堆時經常出現兩種問題:

1) 釋放或改寫仍在使用的記憶體(「記憶體破壞」);

2)未釋放不再使用的記憶體(「記憶體洩漏」)。當釋放次數少於申請次數時,可能已造成記憶體洩漏。****存往往比忘記釋放的資料結構更大,因為所分配的記憶體通常會圓整為下個大於申請數量的2的冪次(如申請212b,會圓整為256b)。

注意,堆不同於資料結構中的」堆」,其行為類似鍊錶。

在實際程式設計中會遇到的與記憶體空間相關的問題

為了解決資料儲存的問題,我們有3種辦法申請空間並使用它們:

我們主要討論前兩個方案  

棧:只要棧的剩餘空間大於所申請的空間,系統將為程式提供記憶體,否則將報異常提示棧溢位。

堆:首先應該知道作業系統有乙個記錄記憶體位址的鍊錶,當系統收到程式的申請時,會遍歷該鍊錶,尋找第乙個空間大於所申請的空間的堆結點,然後將該結點從空閒結點鍊錶中刪除,並將該結點的空間分配給程式。另外,對於大多數系統,會在這塊記憶體空間中的首位址處記錄本次分配的大小,這樣**中的delete或free語句就能夠正確的釋放本記憶體空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會將多餘的那部分重新放入空閒鍊錶中

棧:在windows下,棧是向低位址擴充套件的資料結構,是一塊連續的記憶體區域,棧 頂的位址和棧的最大容量是系統預先規定好的,能從棧獲得的空間較小。

堆:堆是向高位址擴充套件的資料結構,是不連續的記憶體區域,這是由於系統是由鍊錶在儲存空閒記憶體位址,自然堆就是不連續的記憶體區域,且鍊錶的遍歷也是從低位址向高位址遍歷的,堆得大小受限於計算機系統的有效虛擬記憶體空間,由此空間,堆獲得的空間比較靈活,也比較大。

棧:棧由系統自動分配,速度快,但是程式設計師無法控制。

堆:堆是有程式設計師自己分配,速度較慢,容易產生碎片,不過用起來方便。

:在函式呼叫時,第乙個進棧的是主函式中函式呼叫後的下一條指令的位址,然後是函式的各個引數,在大多數的c編譯器中,引數是從右往左入棧的,當本次函式呼叫結束後,區域性變數先出棧,然後是引數,最後棧頂指標指向最開始存的位址,也就是主函式中的下一條指令。

:一般是在堆得頭部用乙個位元組存放堆得大小,具體內容由程式設計師安排。

資料量較小時,推薦使用棧空間申請,即直接定義陣列;資料量稍大或者不確定時,推薦使用堆空間記憶體,即使用malloc或者new動態申請,因 為棧空間常常會有大小的限定,當棧空間耗盡時,棧溢位會導致程式崩潰  當資料量超大的,建議重新審閱演算法或者使用檔案儲存棧空間與子函式,遞迴與棧溢位當乙個子函式被呼叫時,子函式的資料及**都會被裝入棧中,因為棧空間通常會有大小限制,如果子函式太多時,就會有棧溢位的風險。所以當程式設計師考 慮使用遞迴函式解決問題時,應當考慮到棧溢位的風險。

C程式記憶體結構

c程式記憶體結構 乙個32位的執行在保護模式下應用程式 無論是linux 還是windows 都給它分配乙個4gb的平坦的記憶體空間 乙個執行著的c程式所占用的記憶體空間分為 區 初始化資料區 未初始化資料區 堆區 和 棧區 在位址上從高位到地位為 高位 棧區堆區 未初始化資料區 即bbs 資料區 ...

C 程式的記憶體結構

問題引出 在看一些資料部落格的時候說執行緒共享同乙個程序的 段和資料段,又有說法是 段和資料段在可執行檔案中載入,比較疑惑,下面稍微具體的整理一下。int a 0 全域性初始化區 char p1 全域性未初始化區 main int b 棧 char s abc 棧 char p2 棧 char p3...

C 程式的記憶體結構

問題引出 在看一些資料部落格的時候說執行緒共享同乙個程序的 段和資料段,又有說法是 段和資料段在可執行檔案中載入,比較疑惑,下面稍微具體的整理一下。int a 0 全域性初始化區 char p1 全域性未初始化區 main int b 棧 char s abc 棧 char p2 棧 char p3...