這個問題是知乎上的一個問題,看了以后覺得比較有意思,代碼短到只有十多行,但是這么短的代碼卻輸出了很奇怪的結果,很多人回答的時候都是站在理論的角度上說明代碼的問題,但是實際的問題還是沒有說明其中的問題,
問題是“C 語言區域變數,堆與堆疊的問題?”
問題的地址如下:https://www.zhihu.com/question/60415017
知乎上的問題


以上就是知乎中的問題,基本上把問題也描述清楚了,對于它的問題看似詭異,其實并不復雜,這個問題涉及幾個知識點,第一是關于記憶體分配的問題,第二是關于函式呼叫時堆疊幀的開辟與回收的問題,當然了,如果是純理論的描述問題,其實只會把問題越搞越糊涂,如果結合除錯器問題就不同了,
以下是我在知乎的回答(因為當時回答時隨意了一些,所以這里再簡單的整理了一下,從分割線開始,就是我整理過的回答了),
遇到類似的問題,通過在除錯器中進行單步除錯,然后再觀察其反匯編代碼,一般就知道其中的問題所在了,
先來了解幾個簡單的概念性的問題:
首先,區域變數保存在堆疊中;
其次,new 分配的空間在堆中,
堆疊空間是由 ESP 和 EBP 尋址(x86架構的平臺下),這兩個暫存器是由 CPU 控制維護的,ebp 作為堆疊幀的基址來說,函式呼叫完后會自動恢復到被呼叫之前,那么堆疊中的資料其實還是存在的,esp 作為堆疊頂指標,在函式回傳后,也會被識訓,雖然堆疊幀在函式回傳后被回收,但是其中的資料并沒有被回收,因此之前的資料仍然是存在的,很多書上說,訪問這樣的地址會給出隨機值,其實不是,只是這些值我們不再確定是什么值而已,但是它不是隨機的,
new 出來的堆空間,如果不 delete 是不會釋放的,也就是說 new 完以后的地址只要不釋放,在其他代碼中都可以使用,
以上就是 堆 空間和 堆疊 空間的簡單描述,
上面是理論部分,下面實際觀察一下,
我用的環境是 VS2012,和提問者的環境不同,但是程序是相同的,
看一下 func 函式的反匯編代碼,這里我用的 DEBUG 方式編譯的,
在 func 函式的 return 處下斷點,然后運行到此處,觀察其反匯編代碼,并打開暫存器視窗、監視視窗和記憶體視窗,
看下面的截圖:

變數的地址是 0x0103fd6c,而 i 的值是0x0132a670,這值是一個地址,也就是由 new 分配的堆地址,看一下 0x0132a670 這個地址中的值,如下圖:

而 0x0103fd6c 是變數 i 的地址,這個地址在堆疊中,如下圖:

上面的暫存器的值是在 func 函式中的值,看一下 ebp 和 esp 的值,
回傳 main 函式,如下圖:

上圖是回傳 main 函式后的暫存器的值,
再看 0x0132a670 地址中記憶體的值仍然沒變……
這就是堆的效果,即 new 的情況,
這部分記憶體如果不是人為去寫,一般資料不會被修改或覆寫,
前面說的是陣列在堆中的情況,如果是在堆疊中的話,那么陣列 i 的值都在堆疊中,即7、9、5 也在堆疊中,
簡單說一下,
仍然在 func 的 return 處下斷點,運行到這里,觀察:

此時在 func 函式內,繼續單步回傳到 main 函式內:

觀察,現在 ESP 和 EBP 已經恢復到 main 函式的堆疊幀內,而且代碼也運行到了 main 的 for 內,
但是記憶體的堆疊中,func 函式內的 i 陣列仍然存在,雖然堆疊幀被回收,但是資料仍在,通常情況是無法訪問它們的,但是現在把 i 的地址回傳給 main 函式,因此還是可以訪問到它的,

發現執行到完 call 以后,堆疊中的資料被破壞了,因為用的是單步步過,其實只要進入 call 以后,原來堆疊中的資料就被破壞了,
那么為什么 7 能被正確的輸出呢?因為在堆疊還沒破壞之前,7 已經當作 printf 的引數被送入堆疊中當作引數了,看那句 push edx 即可,
剩下的輸出就不說了,反正堆疊已經被破壞了,剩下的就理所當然有問題了,
以上就是我給出問題的答復,其實整個程序還算簡單,記得我在學習的時候,我的老師說過這么一句話,“學編程不看記憶體,相當于游泳不下水”,當然了,也許并不是每門編程語言都有機會去觀察其運行時的記憶體情況,但是,了解如何除錯還是非常有趣的事情,因為很多看似不好解釋的問題,其實在除錯器下面都是可以看到問題本質的,
我的微信公眾號:“碼農UP2U”

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/93398.html
標籤:C++
上一篇:OI之路
下一篇:c++語法大全筆記(一)
