Do you remeber Stack overflow ?

由問題驅動思考,由目標驅動前進的動力。

爲什麼函數調用時會需要棧這種數據結構?
棧結構圖:
在這裏插入圖片描述特點是: 先進後出(FILO),就像打手槍一樣當然得排除左輪這種,例如毛瑟手槍。裝彈的時候,先裝的最後纔打出來。

前面一篇博客中寫到了指令跳轉,我們知道了 if…else 的指令跳轉是跳到某個地方就在那裏執行,直到結束也不會跳回到當初的跳轉點。

你可以點擊這裏進行指令跳轉博客的閱讀

而在函數調用的時候恰恰相反,會跳轉到當初的跳轉點然後向下依次執行。

那問題來了,有什麼辦法能讓指令跳轉回當初的跳轉點呢?就拿這個函數舉例:
在這裏插入圖片描述
方法1: 直接將 sub 函數的語句放置到 main 中調用部分,這樣豈不是不用跳轉指令,而且也能減少 cpu 的指令數了? 性能也會相應的提高。但,如果在 sub 函數中在調用 別的函數,依次遞歸下去,後果可想而知。

方法 2: 利用寄存器,將需要返回的地址記錄下來。這樣是可以解決,但如果有太多的指令地址需要存下來,那針對於 Intel i7 CPU 只有16個64位寄存器,調用的層數一多就存不下了。

主角登場-- 棧 , 這種數據結構很好的解決了以上兩個方法的不足點。
棧的大小是動態生成的,在程序中會分配棧底的地址然後依次向上遞減,所以在程序中棧是倒過來的,即棧底在上面,棧頂在下面。

在 Linux中對上面函數進行反彙編:
在這裏插入圖片描述
在這裏插入圖片描述關鍵字解釋:
每個函數都有自己的一個棧。

push 入棧 (給槍上子彈) , pop(出棧,打槍) , rbp: 棧幀指針(把調用者的棧底地址壓到棧頂) , rsp : 棧頂指針(始終指向棧頂)
mov rbp rsp (即將指向棧底的 rbp 跟隨 rsp 指向棧頂)

先從 main 看:
main 給自己創建一個棧,將rbp , rsp 入棧,並加入相關的局部變量:
在這裏插入圖片描述
直到 call 發生指令跳轉,跳轉到 sub 函數的入口,同樣的道理也會創建一個棧將有關變量壓入。最後將結果 pop() 出來 ret 返回到 main , 繼續執行main函數剩餘的部分。

什麼情況會發生 Stack overflow :

即遞歸調用,向下面這個函數一樣:

public void show()
{
   show();
}

性能調優: (空間換時間)
當調用的層次很少的時候,就可執行上面 方法1 的想法。這樣會減少 cpu 的指令數,但內存的消耗會加重。
在用 gcc 編譯時需要加參數 -O 讓程序自動優化:

gcc -g -c -O test.c 
objdump -d -M intel -S test.o

總結: 瞭解程序棧一個概念,以及在函數調用中它起到的作用。

發佈了50 篇原創文章 · 獲贊 58 · 訪問量 2419
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章