C語言編程: 函數調用中堆棧的個人理解
接下來將通過下面幾個問題解析函數調用中對堆棧理解:
(1)函數調用過程中堆棧在內存中存放的結構如何?
(2)彙編語言中call,ret,leave等具體操作時如何?
(3)linux中任務的堆棧,數據存放是如何?
1. 函數調用過程中堆棧在內存中存放的結構如何?
計算機,嵌入式設備,智能設備等其實都是有軟體和硬體兩部分組成,具體實現也許複雜,但整體的結構也就如此。軟體運行在硬體上,告訴硬體該幹什麼。操作系統軟體是在啟動過程中經過BIOS,bootloarder等(如果有這些過程的話)從磁碟載入到內存中,而自定義軟體則是編寫存放到磁碟中,只有通過載入才會到內存中運行。
首先我們來看一下什麼是堆、棧還有堆棧,我們經常說堆棧其實它是等同於棧的概念。
可以通俗意義上這樣理解堆,堆是一段非常大的內存空間,供不同的程序員從其中取出一段供自己使用,使用之後要由程序員自己釋放,如果不釋放的話,這部分存儲空間將不能被其他程序使用。堆的存儲空間是不連續的,因為會因為不同時間,不同大小的堆空間的申請導致其不連續性。堆的生長是從低地址向高地址增長的。
對棧的理解是,棧是一段存儲空間,供系統或者操作系統使用,對程序員來說一般是不可見的,除非從一開始由程序員自己通過彙編等自己構建棧,棧會由系統管理單元自己申請釋放。棧是從高地址向低地址生長的,既棧底在高地址,棧頂低地址。
其次我們看一下應用程序的載入,應用程序被載入進內存後,由操作系統為其分配堆棧,程序的入口函數會是main函數。不過main函數也不是第一個被調用的函數,我們通過簡單的例子講解。
#include
#include string.h>int function(int arg){ return arg;}int main(void)
{ int i = 10; int j; j = function(i); printf("%dn",j); return 0;}
用gcc -S main.c 生成彙編文件main.s, 其中function的彙編代碼如下:
function:
.LFB0:.cfi_startproc
pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl -4(%rbp), %eax popq %rbp .cfi_def_cfa 7, 8ret
.cfi_endproc
看以看到當函數被調用時,首先會把調用函數的棧底壓棧到自己函數的棧中(pushq %rbp),然後將原來函數棧頂rsp作為當前函數的棧底(movq %rsp, %rbp)。函數運行完成時,會將壓入棧中的rbp重新出棧到rbp中(popq %rbp)。當前function彙編函數沒有顯示出棧頂的變化(rsp的變化),我們可以通過main函數來看棧頂的變化,彙編代碼如下:
main:
.LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6subq $16, %rsp
movl $10, -4(%rbp) movl -4(%rbp), %eax movl %eax, %edi call function movl %eax, -8(%rbp) movl -8(%rbp), %eax movl %eax, %esi movl $.LC0, %edi movl $0, %eaxcall printf
movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc
從上面的彙編代碼可以看到首先也是壓棧和設置新棧底的過程,從此可以看出main函數也是被調用的函數,而不是第一個調用函數。代碼中的黃色部分是當前棧頂變化,從使用的subq可以知道,棧頂的地址要小於棧底的地址,所以棧是從高地址向低地址生長。
接下來可能有點繞,慢慢讀,將用語言描述函數調用過程,調用函數會將被調用函數的實參從右往左的順序壓入調用函數的棧中,通過call指令調用被調用函數,首先將return address(也就是call指令的後一條指令的地址)壓入調用函數棧中,這時rsp寄存器中存儲的地址是存放return address內存地址的下一地址值,這時調用函數的棧結構形成,然後就會進入被調用函數的作用域中。被調用函數首先將調用函數的rbp壓入被調用函數棧中(其實這個地址就是rsp寄存器中存儲的地址),接下來將會將這個地址作為被調用函數的rbp地址,才會有movq %rsp, %rbp指令設置被調用函數的棧底。如上所描述的構成了函數調用的堆棧結構如下圖所示。