1. 程式人生 > >一個有趣的C語言問題

一個有趣的C語言問題

        這個問題是知乎上的一個問題,看了以後覺得比較有意思。程式碼短到只有十多行,但是這麼短的程式碼卻輸出了很奇怪的結果。很多人回答的時候都是站在理論的角度上說明程式碼的問題,但是實際的問題還是沒有說明其中的問題。

 

        問題是“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”

相關推薦

一個資深C語言工程師說如何學習C語言

談及C語言,我想凡是學過它的朋友都有這樣一種感覺,那就是“讓我歡喜讓我憂。”歡喜的是,C語言功能非常強大、應用廣泛,一旦掌握了後,你就可以理直氣壯地對他人說“我是電腦高手!”,而且以後若是再自學其他語言就顯得輕而易舉了。憂慮的是,C語言猶如“少林武功”一般博大精深,太難學了。其實就筆者認為

一個學習C語言的好網站,推薦給大家

你是否在疑問“C語言學習完了以後該怎麼深入學習?” 你是否在疑惑“怎麼學了一年的C語言還是什麼都不會寫?” 你是否在迷茫“C、Java、C#……一大堆語言,我學哪個才能對以後就業有幫助?” 來學《C語言也能幹大事》吧,它將解除你內心的疑惑,伴你走上成功之路。所有視訊、板書都

用弦截法求函式的一個根(c語言描述)

任務和程式碼: 用弦截法求函式x^3-5x^2+16x-80=0的根 /* *Copyright (c) 2016, CSDN學院 *All rights reserved. *檔名:main.c

一個考驗c語言和資料結構功底的小專案

想測一下自己c語言學習水平的朋友可以做一下這個專案試試,能做出來說明c語言已經入門了 #include<stdio.h> #include<stdlib.h> #include<string.h> type

一個簡單C語言的詞法分析器

一個簡單C語言的詞法分析器 語言的詞法構成: 識別符號 id 同C語言識別符號 常量 num 數字 ch 字元 str 字串 關鍵字 kw_int int kw_char char kw_void void kw_if

編寫一個刪除C語言程式中所有註釋語句的程式

#include <stdio.h> void rcomment (int c); void in_comment_one (void); //該函式用於處理/*及*/的註釋符 voi

一個有趣C語言問題

        這個問題是知乎上的一個問題,看了以後覺得比較有意思。程式碼短到只有十多行,但是這麼短的程式碼卻輸出了很奇怪的結果。很多人回答的時候都是站在理論的角度上說明程式碼的問題,但是實際的問題還是沒有說明其中的問題。       &n

一個CGI程序-----完全就是普通的c語言嘛‘(*∩_∩*)′

同學 pat gree ostream 出現 targe 普通 get 方便 第一個CGI程序 ————完全就是普通的C語言嘛 ‘(*∩_∩*)′ PainterQ 2017年5月14日 上一篇博文裏面敘述了Apache的安裝和配置方法,恍恍惚惚我就擁有了自

C語言——輸入一個字符串,將連續數字字符轉換為數字

一個 [0 fine main span nbsp 輸出 print ont 輸入一個字符串,內有數字和非數字字符,例如: A123cdf 456.78cpc876.9er 849.1 將其中連續的數字作為一個實數,依次存放到一數組a中。例如123存放在a[0],456.

通過編寫c語言程序,運行時實現打印另一個程序的源代碼和行號

clas 行號 意義 spa clu 可執行 stdlib.h 讀取 進行 2017年6月1日程序編寫說明: 1.實現行號的打印,實現代碼的讀取和輸出,理解主函數中的參數含義。 2.對fgets函數理解不夠 3.對return(1); return 0的含義理解不夠 4.未

C語言中的一個*和[]優先級問題

pre 執行 return ges spa 技術 分享 malloc bsp 最近寫著玩了這麽一段代碼 1 int Init(int **T, int v1, int v2, int v3) 2 { 3 4 if (!(*T=(int*) malloc(3*

自己主動化測試程序之中的一個自己定義鍵盤的模擬測試程序(C語言

nds per oid 尾指針 應用 tro scan number 實現 一、測試程序編寫說明 我們做的終端設備上運行的是QT應用程序。使用自己定義的鍵盤接口。經過測試人員長時間的人機交互測試,來確認系統的功能是否滿足需求。如今須要編寫一個自己主動化

使用Xcode實現第一個C語言程序——Hello world

family -h bsp ati const 是我 return too 主動 近期一直使用Xcode學習OC,Swift。並開發iOS應用。閑來無趣,想在Mac上寫幾個C程序。曾經在Windows中,我們常用VC++,Visual Studio,等等C或

C語言一個語句判斷大小端

urn ref div .org oid iter end doc 語句 1 int isLittleEndian(void) 2 { 3 return *(char *)(int []){1} == 1; 4 } 關於Compound Literals,h

C語言多線程的一個簡單例子

color oid blog stdlib.h null bsp 等待 creat 多線程   多線程的一個簡單例子:    #include <stdio.h> #include <stdlib.h> #include <string.h&

C 語言一個錯誤,沒找出原因

amp n) string getc bsp can 什麽 數據 能夠 #include <stdio.h>#include <stdlib.h>#include <string.h> intmain(void){ char str[51

C語言一個字符數組裏面的所有元素變成一個字符串

num des urn ber bold 字符數 proc repr 目標 #include <string.h> int main() // 這裏為了方便直接用main函數 { char array[] = { ‘h‘, ‘e‘, ‘l‘, ‘l‘,

C語言實現將一個字符串翻轉

spa highlight amp false main lag temp break bcd 問題: 對於字符串char* = " abcd efg h"; 要求輸出" h efg abcd "; 字符串整體翻轉,但是裏面每一個單詞的順序不翻轉 思想:&

樹莓派進階之路 (032) -字符問題(2) - 用c語言怎樣得到一個漢字的GB2312編碼(轉)

十六進制 字符串 c++ gb2 十進制 轉換 tails 表示 blog C/C++支持的是ASCII,不過漢字編碼中,GB2312與ASCII是兼容的,所以可以在C中獲得漢字的GB2312編碼 GB2312是兩個字節的,第一字節是高八位,第二字節是低八位,比如下面的程序

C語言程序】今天是祖國母親的生日,特意編寫一個小程序,為祖國母親慶生~

一個 img ges birt efi people print log blog #include <stdio.h>#define N 80 int main(int argc, char *argv[]) {char a[N];printf("Hello,