C語言函數調用過程中的內存變化解析

2020-12-18 電子發燒友

C語言函數調用過程中的內存變化解析

TOMORROW 星辰 發表於 2020-12-11 16:21:13

相信很多編程新手村的同學們都會有一個疑問:C 語言如何調用函數的呢?局部變量的作用域為什麼僅限於函數內?這個調用不是指C 語言上的函數調用的語法,而是在內存的視角下,函數的調用過程。本文將從C 語言調用實例,內存視角,反彙編代碼來探討C 語言函數的調用過程,也可以說是C 語言函數調用過程圖解。通過這個C 語言函數調用過程圖解,同學們將會知道,C 語言函數在調用時,內存空間是怎樣變化的。

要想理解這一個過程還好涉及到函數棧幀的概念。函數棧幀指的是,在調用函數時,系統在棧空間中給函數所分配的一段連續空間。其中 ebp(棧幀基址指針)則是指明了當前函數的棧幀基地址,對函數的資源(局部變量、實參等)的訪問,都要通過 ebp+offset(偏移量)來進行訪問。而 esp 則是棧指針,指示當前棧空間棧頂的位置。

以下代碼即是此次探討 C 語言函數調用過程的實例源碼:

int subFunc(int abc){        int def=0x9999;        abc=0x8888;        return abc;} int _tmain(){         subFunc(0x2222);               return 0;}

源碼很簡單,在一個主函數中,調用一個帶參數的子函數。源碼使用 Visual Studio2010 進行調試,並同時查看內存窗口、反彙編窗口及變量窗口。

進入調試模式,並將斷點定在調用子函數 subFunc()處,然後運行並觀察。

通過觀察窗口,可以知道,此時還是在執行 main 函數,而 ebp(棧幀基址指針)指向的是 0x0073fb64,esp=0x0073fa98。從反彙編代碼可以看到,在調用函數前,需要先將參數壓棧,也就是將實參存到了 0x0073fa94 處,然後再調用到子函數。

進入到子函數時,esp 已經變成了 0x0073fa8c,而 0x0073fa90 處存放的是,子函數執行完後返回到 main 函數中的地址。進入到子函數後,先將 main 函數的 ebp 壓棧,然後將當前棧頂指針的值賦值給 ebp 作為當前子函數的 subFunc()的棧幀基址指針。此時 esp 和 ebp 都變成了 0x0073fa8c。

緊接著,可以看到,esp 一下子被減去了 0x0cch,也就是說棧空間一下子增長了 0x0cch,並且這段空間全部被賦值為 0xcc。再往下看,可以看到子函數中的局部變量被分配在了 0x0073fa84 處(因為變量是 32 位的,然後 CPU 卻是 64 位的,所以空了 32 位不作使用),也就是說,這一段被初始化為 0xcc 的棧空間是被用來給局部變量分配空間的。

接下來再看,在 main 函數傳遞了一個實參 0x2222 給子函數 subFunc 中的形參 abc。在對 abc 進行讀寫時,其實就是在對前面實參所被存儲的空間進行讀寫,也就是說形參在作為參數也作為局部變量的同時,它所被分配的內存空間是在函數棧幀基址 ebp 之下。

而子函數被執行完後,返回的過程則是一個與上面過程相逆的過程。將相應的數據出棧,恢復 ebp 等信息,釋放子函數的棧空間,返回到主函數。所以局部變量的作用域只是在函數中,當函數被執行完返回時,函數的棧幀都被釋放了,局部變量等數據也就沒有了,不存在了,也就是說局部變量的生命周期是與函數的生命周期等同的。

經過以上的 C 語言函數調用過程圖解,相信已經理解了 C 語言在內存中是如何調用的了。然後可以總結並得出下面的函數調用的棧幀圖解。從函數調用的層面看,棧空間是被從下往上一塊一塊地增長的,並且是後分配的先被釋放,先分配的後被釋放。

編輯:hfy

打開APP閱讀更多精彩內容

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容圖片侵權或者其他問題,請聯繫本站作侵刪。 侵權投訴

相關焦點

  • C/C+編程筆記:在C+中如何調用C語言的代碼?你可以這樣做
    很多初學的小萌新,第一反應很有可能是:直接調用就完事了!這樣說也沒錯,因為C++本來就包含了C。比如在C文件中存在一個函數func_c(), 該文件與C++的工程混編在一起時,可以直接在C++中調用C文件中的func_c();不需要做任何額外處理。
  • C語言編程核心要點
    類型C是強類型語言,有short、long、int、char、float、double等build-in數據類型,類型是貫穿c語言整個課程的核心概念。struct、union、enum屬於c的構造類型,用於自定義類型,擴充類型系統。變量變量用來保存數據,數據是操作的對象,變量的變字意味著它可以在運行時被修改。
  • C/C+時間相關的函數
    大家平時工作,如果有計算函數耗時或者列印當前時間的需求,一定要來看看這篇文章! 首先介紹下C++標準中的chrono庫 是一個關於時間的庫,起源於,現在是的標準,話說現在的標準好多都是源於,要進標準的特性似乎都會先在試驗一番。
  • c語言中malloc申請的空間和直接定義變量申請的空間有什麼區別?
    根據以前的編程經驗,要點有三點:一是空間分配的連續性;二是動態內存申請;三是防止程序執行中出現異常錯誤。以下分別說明。直接定義變量與malloc定義變量的編程含義;malloc事先分配好了內存空間。在c語言中,malloc函數原型為void*malloc(unsignedintslong),其作用是在內存的動態存儲區中分配一個長度為slong的連續空間。
  • C語言中「c=a+b」,這種結構合理嗎?
    我們從側面以及原理來解析一下。 編譯系統是無法搞明白的; 因此它就認為++是一個缺少左值的自增運算符,於是提示錯誤給用戶:lvalue required as increment operand 原理解析一下 C語言在這裡遵循詞法解析的貪婪匹配原則。
  • c編譯器so easy,gcc c編譯器生成、使用動靜態庫
    靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此代碼體積較小。main.c(見程序3)為測試庫文件的主程序,在主程序中調用了公用函數hello。
  • 你們要的C語言精華,年薪100萬的行業
    在許多語言中根本就沒有這個概念。但是它卻正是C靈活,高效,在面向過程的時代所向披靡的原因所在。因為C的內存模型基本上對應了現在von Neumann(馮·諾伊曼)計算機的機器模型,很好的達到了對機器的映射。不過有些人似乎永遠也不能理解指針【注1】。
  • 箭頭函數=> 的使用與局限 - ES6中JavaScript新特性之函數
    我們知道,函數調用會在內存形成一個「調用記錄」,又稱「調用幀」(call frame),保存調用位置和內部變量等信息。如果在函數`A`的內部調用函數`B`,那麼在`A`的調用幀上方,還會形成一個`B`的調用幀。等到`B`運行結束,將結果返回到`A`,`B`的調用幀才會消失。如果函數`B`內部還調用函數`C`,那就還有一個`C`的調用幀,以此類推。
  • php中函數禁用繞過的原理與利用
    php index.php來調用會發現system執行成功,但如果我們通過頁面來訪問則會發現執行失敗,這是一個在利用過程中需要注意的點,這也就是為什麼我們會使用管道符來選取execve。':993/imap/ssl}INBOX', $_POST['login'], $_POST['password']);那麼該函數在調用時會調用rsh來連接遠程shell,而在debian/ubuntu中默認使用ssh來代替rsh的功能,也即是說在這倆系統中調用的實際上是ssh,而ssh中可以通過-oProxyCommand=來調用命令,該選項可以使得我們在連接伺服器之前先執行命令
  • 詳解C語言數據類型:float與double
    當較小尺寸的參數(特別是char,short和float)傳遞給可變參數函數(如printf之類的函數,其參數數量不固定)時,它們將轉換為較大尺寸。Char和short轉換為int,float轉換為double。為什麼這樣 據我所知,純粹出於歷史原因。
  • C 語言,你真的懂遞歸了嗎?
    要說到遞歸如果不說棧的話,我覺得有點不合適,遞歸特點就是不斷的調用同一個函數,如果這個函數沒有一個遞歸界限,那麼就是死循環了,所以討論遞歸,就必須要討論遞歸的界限,就是限定這個遞歸調用多少次。if(count > 0){所以他的調用順序可以用這個圖示來說明
  • java創建對象的過程詳解(從內存角度分析)
    (從內存角度去分析:重點)重點一、基本知識我們知道,一個對象的創建過程包含兩個過程:初始化和實例化我們在使用一個對象時,JVM首先會檢查相關類型是否已經加載並初始化,如果沒有,則JVM立即進行加載並調用類構造器完成類的初始化。在類初始化過程中或初始化完畢後,根據具體情況才會去對類進行實例化。
  • C語言|文件指針、fopen()、fscanf()、fclose()
    這是一個簡單的文件系統fscanf()函數的功能是把磁碟文件數據讀出保存到變量(內存)每一個文件都有自己的FILE結構和文件緩衝區exit(0)是系統標準函數,作用是關閉所有打開的文件參數0表示程序正常結束,非0參數通常表示不正常的程序結束C語言允許同時打開多個文件,不同文件採用不同文件指針指示,但不允許同一個文件在關閉前被再次打開如圖所示,fgets()函數用來從文本文件中讀取字符串,調用格式為:fgets(s,n,fp);其中s可以是字符數組名或字符指針(指向字符串的指針
  • 深入考察解釋型語言背後隱藏的攻擊面,Part 2(二)
    接上文:在本文中,我們將深入地探討,在通過外部函數接口(Foreign Function Interface,FFI)將基於C/C++的庫「粘合」到解釋語言的過程中,安全漏洞是如何產生的。
  • C語言程序設計試題及答案
    A) 程序行 B) 語句 C) 函數 D) 字符2、C語言規定,在一個源程序中main函數的位置( )。A) 必須在最開始 B) 必須在系統調用的庫函數的後面C) 可以任意 D) 必須在最後3、下列符號串中符合C語言語法的標識符是( )。
  • 乾貨——聊聊內存那些事(基於單片機系統)
    , 靜態區的內存直到程序全部結束之後才會被釋放 l  堆區:由程式設計師調用malloc()函數來主動申請的,需使用free()函數來釋放內存,若申請了堆區內存,之後忘記釋放內存,很容易造成內存洩漏
  • C語言怎麼樣?今天聊聊C語言的發展史!
    它通過更多的系統調用和更多的命令擴展了第一版。此版本還看到了C語言的開始,該語言用於編寫一些命令。 此處的代碼僅是某些命令,某些庫函數和C編譯器的原始碼。c /中的文件來自 last1120c.tar.gz 磁帶,並構成了第二版Unix的有效C編譯器。
  • 【揭秘】C語言類型轉換時發生了什麼?
    在C語言中,數據類型指的是用於聲明不同類型的變量或函數的一個廣泛的系統,我們常用的算術類型包括兩種類型:整數類型和浮點類型。那麼相互之間具體是怎麼轉化的呢?下類型轉換 不同數據類型的存儲大小和值範圍是不一樣的,程序在初始化的時候就已經設定了,例如: int a = 9;float b = 8.5; a,b佔的字節大小不一樣,這個我們應該都知道,在C語言中一個表達式允許不同類型的數據進行運算
  • C語言你學「廢」了嗎?
    然後你慢慢的進入老師的節奏,初識C語言 / 了解C語言的發展史,搭建C開發環境認識一個簡單的C程序 / 一般都從「Helloworld"這個簡單的C程序開始算法入門 / 算法基本概念,簡單程序結構,流程圖等基本數據類型 / 整型,浮點型,字符型運算符與表達式 / 各種運算符以及優先級,表達式和語句等簡單輸入輸出函數 / 目前主要是scanf和printf,getchar和putchar選擇
  • Go 語言之 defer 的前世今生 - CSDN
    作者 | 歐長坤來源 | 碼農桃花源延遲語句 defer 在最早期的 Go 語言設計中並不存在,後來才單獨增加了這一特性,由 Robert Griesemer 完成語言規範的編寫 [Griesemer, 2009], 並由 Ken Thompson 完成最早期的實現 [Thompson, 2009],兩人合作完成這一語言特性。