【c語言】函數調用棧

我們常常會使用函數調用來實現某種操作,只知道從調用函數跳到被調函數,被調函數執行完又返回到主調函數,可能會有以下的疑問:                                                                                                                                                                                                                1.形參開闢空間嗎?
2.參數由誰來開闢空間
3.形參的入棧順序
4.返回值由誰傳出來
5.怎麼回到調用函數
6.如何知道下一行指令

爲了解決以上的問題,以一段簡單的代碼爲例,以下是代碼的反彙編:

調用函數:

 

mov    指移動數值
lea  指移動地址
ebp  指向棧底指針
esp  指向棧頂指針
eax ebx ecx edx  都是存儲數據的寄存器

 

先從這部分彙編指令開始,

    int rt = 0;
0123141E  mov         dword ptr [rt],0  
    int a = 10;
01231425  mov         dword ptr [a],0Ah  
    int b = 20;
0123142C  mov         dword ptr [b],14h  
這三個彙編指令都是對變量進行初始化,如 Int rt = 0;是根據棧底指針的偏移量確定存儲0這個數字的位置

0123141E  mov         dword ptr [rt],0  
0123141E 是指令的虛擬地址,
mov是對數值的移動,
dword ptr [rt]  實際上是dword ptr [ebp-4](棧底指針的偏移量)
簡單來講,意思就是將數值0移動到爲ebp-4的地址;int a = 10;與int b = 20亦如此入棧

rt = Sum(a,b);
01231433  mov         eax,dword ptr [b]  
01231436  push        eax  
01231437  mov         ecx,dword ptr [a]  
0123143A  push        ecx  
0123143B  call        Sum (012311C7h)  
01231440  add         esp,8  
01231443  mov         dword ptr [rt],eax  

將b的值(ptr[ebp-12])保存在eax寄存器中,然後壓棧,將a的值(ptr[ebp-8])保存在ecx的寄存器中,將ecx壓棧;我們發現這裏的a和b也就是形參,可知,參數是在主調函數中開闢空間的

call:靜態相對位移,call是跳轉到被調函數的指令地址包含以下兩個步驟:
1.將下一條指令壓棧
2.跳轉到被調函數;Sum(012311C7h)括號裏就是被調函數的指令地址

    被調函數

     

會注意到被調函數中的前面一部分和主調函數中的除了個別數據外,基本一致;其實每個函數前面都會這麼一個部分,現在來看這些指令到底是做什麼的;

012313D0  push        ebp  
012313D1  mov         ebp,esp  
012313D3  sub         esp,0C0h  
將主調函數的棧底指針的值壓棧,然後將ebp移動到esp的位置;sub意思是讓esp減等0c0,也就是爲新棧開闢了192個字節;這三步主要就是先將主調函數的棧頂指針入棧,然後爲新棧開闢空間;

012313D9  push        ebx  
012313DA  push        esi  
012313DB  push        edi  
分別將寄存器ebx、esi、edi依次入棧

012313DC  lea         edi,[ebp-0C0h]  
使edi指向ebp-0C0h的位置;

012313E2  mov         ecx,30h  
012313E7  mov         eax,0CCCCCCCCh  
012313EC  rep stos    dword ptr es:[edi]  
 這三行指令的意思就是循環30h十進制是48次(48*4就是新棧開闢的空間大小),從棧頂到棧底將數據賦值爲0xcccccccch

012313EE  mov         eax,dword ptr [a]  
012313F1  add         eax,dword ptr [b] 

在ptr[ebp+8]中將a的值放在寄存器eax中,add是寄存器eax等於eax中的值加上ptr[ebp+c]中的值,也就是計算a+b的值,將結果放在eax中,所以結果是由寄存器帶出來的;如果返回值是小於等於四個字節,則由一個寄存器帶出來,如果大於4小於8的字節,由兩個字節帶出來,大於8個字節時,在調用方開闢一個返回值臨時量的空間,將return回來的值循環拷貝在臨時量區;

銷燬棧

013613F4  pop         edi  
013613F5  pop         esi  
013613F6  pop         ebx  
013613F7  mov         esp,ebp  
013613F9  pop         ebp  
先將三個寄存器出棧,將esp移動到ebp,這時esp和ebp都是指向新棧的棧底指針,最後將ebp出棧,ebp就再次指向主調函數的棧底;

回到主調函數中                                                                                                                                                                                                       01361440  add         esp,8  
01361443  mov         dword ptr [rt],eax                                                                                                                                                                 esp指向的還是新棧的棧底,所以add就是將esp向下移動兩個字節,指向主調函數的棧頂                                                                         將寄存器eax的值賦給rt(ptr[ebp-4])的位置

 

整個過程就是這個樣子了,再回到開始的幾個問題,在這個過程中已經解決了
1.形參開闢空間嗎?形參開闢空間
2.參數由誰來開闢空間?參數由主調函數開闢
3.形參的入棧順序?由右向左
4.返回值由誰傳出來?由寄存器傳出來
5.怎麼回到調用函數?將主調函數的棧底指針的值入棧到被調函數中,被調函數的棧底指針釋放後,回到主調函數中
6.如何知道下一行指令?call指令的作用就是將下一行指令地址入棧

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章