【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)
學習交流加
- 個人qq:
1126137994 - 個人微信:
liu1126137994 - 學習交流資源分享qq群:
962535112
在我之前學習底層的知識的時候,也寫過相關的內容。可以對比的學習:【軟件開發底層知識修煉】二十 深入理解可執行程序的結構,【軟件開發底層知識修煉】二十三 ABI-應用程序二進制接口三之深入理解函數棧幀的形成與摧毀
學習本文的前提是了解進程的內存布局空間。可以看上面兩篇博客進行鞏固
文章目錄
- 1 程序中的棧內存結構
- 1.1 函數的調用過程對應的棧的變化
- 1.11 函數調用棧上的數據
- 2 程序中(應該稱為進程中才對)的堆結構
- 3 程序中的靜態存儲區(其實就是數據區)
- 4 總結
1 程序中的棧內存結構
- 棧在程序中用于維護函數調用的上下文
- 函數中的參數和局部非靜態變量存儲在棧上
- esp指針始終指向棧頂
- ebp是函數棧幀,用于定位的,使用ebp可以查找到函數的參數,返回地址等信息。后面的函數調用分析會認識到這一點
- 棧在整個進程內存空間中是從上往下擴展,也就是從高地址往低地址擴展。如下圖是一個棧
push 操作相當于往棧中填數據,esp指針會向下走。pop操作相當于將棧頂數據彈出,esp指針會網上走。
那么棧對于程序而言,到底有什么用呢?
棧用于保存一個函數調用的時候所需要維護的信息,包括函數的參數、返回地址、局部變量、上下文信息等。它們在棧中的位置大致如下圖所示:
1.1 函數的調用過程對應的棧的變化
每次函數調用都對應一個棧上的活動記錄,被調用函數的活動記錄位于調用函數的下面。比如有如下的幾個函數調用:
那么在某一個時刻,棧中的內容大致是這樣的:
注意:上面的棧中的內容并沒有具體,只是說明各個函數的活動記錄信息在哪個文職,具體里面的內容沒有列出。看下面的分析:
從程序開始運行時分析:
當main開始運行的時候,main函數的棧的信息是下面這樣的:
- 可以看到,首先入棧的是函數參數,然后是函數的返回地址
- old_ebp代表調用main函數的ebp的位置。這個暫且不管
- 可以看到,ebp向上偏移4字節(在X86 32位系統中,棧是以4字節為單位進行存儲數據,所以一次偏移就是4字節)就能找到返回地址(這個返回地址是調用main函數的那個函數之前執行指令的地址)。ebp向下偏移4字節就是old_ebp。所以說ebp是函數棧幀,用于定位查找其他參數
- esp始終指向棧頂
當main函數執行到調用f() 函數的時候,main函數的活動記錄(寄存器,返回地址等)需要保存入棧,f() 函數的參數信息需要入棧。如下圖所示:
可以看到:
- f函數的參數先入棧
- 然后返回地址入棧(main函數調用f()函數那里的地址)
- 然后main函數的ebp的地址入棧。用于定位上一個函數的ebp
- 然后才是函數中的局部非靜態變量信息入棧。這個參數的入棧順序可以參考本文開頭給出的兩篇文章中的內容
f函數調用g函數就是一樣的過程,這里就不再贅述。下面直接上當f執行完返回的過程是怎樣的?
上述f返回后當前棧就只有白色空白部分,下面的深顏色(橘黃色???分不清)并不是當前棧的內容了
可以看出;
- 通過f函數棧幀中的old_ebp找到main函數的ebp,然后將當前ebp寄存器指向它。
- 通過f棧幀中的返回地址找到main函數之前被中斷的地址處,main函數繼續執行。關于這里,非常詳細的文章請參考:【軟件開發底層知識修煉】二十三 ABI-應用程序二進制接口三之深入理解函數棧幀的形成與摧毀
1.11 函數調用棧上的數據
函數調用棧上的數據,在函數返回時,將被釋放,不再有效。所以對于以下代碼,是錯誤的代碼;
2 程序中(應該稱為進程中才對)的堆結構
- 堆空間是進程的內存空間中一塊預留的內存空間,供程序在運行的時候動態分配。
- 使用malloc與free進行堆中的內存的動態申請(具體的使用可以參考這篇文章:【C語言進階深度學習記錄】三十三 C語言中動態內存分配
對于堆空間,本文只簡單的講述系統對堆空間的管理方式(也就是使用malloc時,系統是如何申請內存的,以及系統對內存的管理方式,當然本文也是簡單描述,具體可以參考CSAPP書籍中相關章節)
操作系統對堆的管理方式主要有:
- 空閑鏈表法、位圖法、對象池法
下面主要簡要說明空閑鏈表發的原理:
空閑鏈表就是操作系統將整個可用的堆內存空間分為一塊一塊的,對應的相同數量的指針指向各個內存塊,然后內存塊的末尾又是一個指針指向下一個內存塊的頭。
其實簡單來說就是將多個內存塊串聯成一個鏈表形式。
如下圖所示:
當使用malloc函數進行內存分配的時候,系統會遍歷鏈表,找到能夠滿足申請大小的且沒有被別人申請的空閑的鏈表的一個節點對應的內存塊。比如上圖,申請一個4字節的內存,最終遍歷鏈表找到了一個大小為5字節的內存塊,然后系統就為我們在該內存塊上申請4字節的內存空間供指針p使用。
簡單來說,空閑鏈表法就是上述的大致過程。
想要深入了解操作系統對堆空間的管理,可以閱讀書籍《深入理解計算機系統》(csapp)。本文不再重復贅述。
3 程序中的靜態存儲區(其實就是數據區)
我們一般說進程,但這里說程序。有些術語描述真的是很模棱兩可,但是只要自己明白就行
在進程的地址空間,棧,堆是程序運行時的效果。我們也知道這兩個內存對應的數據到底是什么。
那么對于下面程序中的變量g_init_v,g_uninit_v,s_v1,s_v2以及字符串字面量,它們是存儲在哪里呢?
我們已經看到了上圖中,它們在可執行文件中都是存儲在.data區,.bss區。這些區域。
當程序運行起來之后,它們還是會在進程的內存空間的.data與.bss段。與可執行文件的data與bss名字一樣如下圖
我們稱之為靜態存儲區。為什么叫靜態?并不是因為static,而是因為它們的值雖然有可能被改變,但是卻一直在那個區域。不像棧區域的值最后會被銷毀,堆上的值也需要free。
現在終于明白靜態存儲區實際就是.data與.bss區域了。
下面總結一下靜態存儲區的幾點重要知識:
4 總結
棧,堆與靜態存儲區是進程地址空間的基本數據區域。
- 一定要注意區分可執行文件中的內容與程序運行起來后的進程地址空間中的內容的差別。
- 本文所描述的就是進程地址空間中內容。可執行文件的內容可參考書籍《程序員的自我修養》
總結
以上是生活随笔為你收集整理的【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue 中如何引入字体(思源黑体)
- 下一篇: 由Google Protocol Buf