精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

函數調用時底層會發生什么

jf_78858299 ? 來源:碼農的荒島求生 ? 作者:碼農的荒島求生 ? 2023-02-17 14:47 ? 次閱讀

有讀者問題函數調用是如何實現的,今天就來聊聊這個比較簡單的問題。

大家都應該打包過東西吧,搬家之類的,通常都是找幾個箱子一股腦裝進去,為了不讓箱子占地方,你通常會把它們摞好,就像這樣:

最先被打包好的箱子被摞在最下方,剛打包好的箱子總是放在最上方,這就形成了一種first in last out的結構,也就是我們所說的棧,stack,上面的這些箱子就形成了棧。

如果你懂得用箱子打包東西,你就能明白函數調用是怎么一回事。

原來,在程序運行時每個被調用的函數都有自己的一個箱子,假設這段代碼是這樣寫的:

void D() {}
void C() {
D();
}
void B() {
C();
}
void A() {
B();
}

函數A調用函數B、B調用C、C調用D,那么當函數D在運行時內存中就會有四個箱子,每個函數一個:

圖片

每個函數占據的這個箱子——也就是這塊內存,就被稱為棧幀,stack frame,只不過由于引力的作用,我們摞箱子時是從下往上增長,而出于內存布局的需要,函數調用時的棧是從高地址向低地址增長。

這些箱子中都裝有什么呢?你在函數中定義的局部變量就裝在這里,關于棧幀內容更詳細的講解你可以參考這里《函數調用是在內存中是什么樣子》,這些不是本文的重點,這里更關心的是這些棧幀是怎樣增長以及減少的。

仔細觀察上面這張圖,每個箱子最重要的信息有兩個, 你至少需要知道箱子的底部以及箱子的頂部在哪里 !

圖片

在計算機中,每個函數棧幀的“底部”和“頂部”的信息——也就是內存地址,分別存放在兩個寄存器中:BasePointer(BP)寄存器以及StackPointer(SP)寄存器,即我們熟悉的rbp以及rsp,32位下為ebp以及esp,注意本文以x86_64為例。

圖片

只要確定了rbp和rsp你就能得到一塊棧區,在這塊棧區上就可以進行函數調用:

圖片

讀到這里肯定有的同學可能會問,CPU中的寄存器不是有限的嗎?從這里的講解看每個棧幀都需要維護一個“棧頂”與“棧底”的信息,每個核心中的rbp以及rsp寄存器就一個,我們該怎樣確保函數運行時相應棧幀使用的rbp以及rsp是正確的呢?

方法非常簡單,調用函數時會創建新的棧幀,此時需要將原有rbp寄存器中的值保存在新的棧幀上,就像這樣:

圖片

上圖就是函數調用時第一件要完成的事情,把rbp的值push到棧上,rsp下移,然后呢?然后也很簡單,只需要把rsp指向的地址也賦值給rbp即可,這樣就開啟了一個新的棧幀:

圖片

完成上述操作的有兩條機器指令(gcc編譯器):

push   %rbp
mov %rsp,%rbp

如果你去看編譯器為每個函數生成的機器指令,那么開頭幾乎都是這兩條指令,現在你應該明白這兩條指令的作用了吧。

這兩條指令就把上一個棧幀的rbp的保存到了新的棧幀,由于此時rsp已經指向了新的棧幀棧頂,由于此時棧為空,因此棧頂和棧底的地址是一樣的,可以直接把rsp賦給rbp,這樣一個全新的棧幀就創建出來了。

如果我們在被調函數內部創建一些局部變量:

void funcB() {
int a = 1;
int b = 2;
int c = 3;
...
}

那么此時棧會進一步擴大,并把局部變量存放在該函數的棧幀中:

圖片

現在我們的??梢噪S著函數調用而增長,可以看到,棧幀和你搬家時用的紙箱子還是不太一樣的,函數棧幀不會一開始就大小固定好,而是隨著指令的執行動態增加,也就是如果你往棧上push一些數據,棧幀就會相應的增大一點。

那么函數調用完成時該怎么辦呢?這也非常簡單,只需要一條機器指令:

leave

我們在上一篇棧區分配內存快還是堆區分配內存快中講解了一部分,leave指令的作用是將棧基址賦值給rsp,這樣棧指針指向上一個棧幀的棧頂,然后pop出rbp,這樣rbp就指向上一個棧幀的棧底:

圖片

看到了吧,執行完leave指令后rbp以及rsp就指向了上一個棧幀,這就相當于棧幀的彈出,這樣stack 1占用的內存就無效了,沒有任何用處了,顯然這就是我們常說的內存回收,因此簡單的一條leave指令即可把棧區中的內存回收掉。

圖片

而在x86平臺,leave指令后往往跟上一條ret指令:

leave
ret

我們已經了解了leave指令的作用,這條指令讓rbp以及rsp指向上一個棧幀,然后呢?顯然CPU應該從funcA調用函數funcB之后的一行代碼處繼續運行,那么這行代碼的地址在哪里呢?顯然就在funcA棧幀的棧頂:

圖片

當CPU執行call指令時會把該函數的返回地址push到棧中,而ret指令的作用正是將棧頂彈出(pop)到rip寄存器,rip寄存器告訴CPU接下來該從哪里執行機器指令,這個返回地址是funcA調用funcB時push到棧上的,這樣當從函數funcB()返回后我們就知道該從哪里繼續執行機器指令了,這就是ret指令的作用,當然這里也是函數調用實現的基本原理。

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

    關注

    115

    文章

    3720

    瀏覽量

    80365
  • 函數
    +關注

    關注

    3

    文章

    4238

    瀏覽量

    61976
  • 函數調用
    +關注

    關注

    0

    文章

    19

    瀏覽量

    2576
收藏 人收藏

    評論

    相關推薦

    C語言使用函數調用的知識點

    C語言使用函數調用,我們再熟悉不過了,但是函數調用在內存中究竟發生了什么真的清楚嗎?只有搞清楚內存里的內幕,才算完全搞懂
    發表于 09-07 11:47 ?755次閱讀

    C函數調用機制與棧幀原理詳解

    當一個C函數調用時,函數的參數如何傳遞、堆棧指針如何變化、棧幀是如何被建立以及如何被消除的,一直缺乏系統性的理解,因此決定花時間學習下函數調用時
    發表于 06-08 10:49 ?1003次閱讀
    C<b class='flag-5'>函數</b><b class='flag-5'>調用</b>機制與棧幀原理詳解

    如何查看及更改函數/函數塊的調用環境

    是循環執行,當一個功能塊被多個外部函數/函數調用時,我們應如何查看某一次調用時的內部變量呢?這涉及到函數塊的
    的頭像 發表于 11-17 09:08 ?669次閱讀
    如何查看及更改<b class='flag-5'>函數</b>/<b class='flag-5'>函數</b>塊的<b class='flag-5'>調用</b>環境

    如果使用FCALL調用函數而使用RET返回的話, 就會發生CSA泄露怎么解決?

    FCALL調用函數不會自動存儲Upper Context, 需要使用FRET進行返回, 如果使用FCALL調用函數而使用RET返回的話, 就會發生
    發表于 01-26 07:57

    應用程序調用底層驅動

    本片主要講述了嵌入式linux操作系統的上層應用程序是如何調用底層驅動程序的。
    發表于 03-14 15:00 ?0次下載

    03 底層函數

    03 底層函數
    發表于 10-11 09:29 ?7次下載
    03 <b class='flag-5'>底層</b>庫<b class='flag-5'>函數</b>

    內聯函數和外聯函數有什么區別

    內聯函數是指用inline關鍵字修飾的函數。在類內定義的函數被默認成內聯函數。內聯函數從源代碼層看,有
    發表于 12-15 11:52 ?5731次閱讀
    內聯<b class='flag-5'>函數</b>和外聯<b class='flag-5'>函數</b>有什么區別

    詳解python普通函數創建與調用

    函數是一種僅在調用時運行的代碼塊。您可以將數據(稱為參數)傳遞到函數中,然后由函數可以把數據作為結果返回。
    的頭像 發表于 03-01 16:32 ?1784次閱讀

    C語言使用函數調用在內存中究竟發生了什么?

    C語言使用函數調用,我們再熟悉不過了,但是函數調用在內存中究竟發生了什么真的清楚嗎?只有搞清楚內存里的內幕,才算完全搞懂
    的頭像 發表于 01-13 14:09 ?974次閱讀

    C語言函數調用的形式及過程

    C語言函數調用時的數據傳遞 在調用有參函數時,主調函數和被調函數之間有數據傳遞關系。
    的頭像 發表于 03-10 14:28 ?1490次閱讀

    什么是函數調用?

    函數調用,就是使用我們已經定義好的函數,或者C語言自帶的庫函數。
    的頭像 發表于 04-04 17:21 ?5220次閱讀

    SCL中調用函數的示例

    在此,可插入函數 (FC) 調用函數塊 (FB) 調用。函數塊可作為單實例、多重實例或參數實例進行調用
    的頭像 發表于 06-06 10:18 ?1884次閱讀

    網絡系統調用網絡套接字入口函數

    調用的應用層接口函數,第二個參數是一個指針,指向具體被調用函數(如accept函數)所需要的參數。 這些在用戶系統
    的頭像 發表于 07-24 11:02 ?390次閱讀

    ES32F36xx芯片發生HardFault異常時的函數調用關系及問題定位

    ES32F36xx芯片發生HardFault異常時的函數調用關系及問題定位
    的頭像 發表于 11-06 17:13 ?597次閱讀
    ES32F36xx芯片<b class='flag-5'>發生</b>HardFault異常時的<b class='flag-5'>函數</b><b class='flag-5'>調用</b>關系及問題定位

    python定義函數調用函數的順序

    定義函數調用函數的順序 函數被定義后,本身是不會自動執行的,只有在被調用后,函數才會被執行,得
    的頭像 發表于 10-04 17:17 ?1041次閱讀