程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 函數調用過程

函數調用過程

編輯:關於C語言

一個函數調用另一個函數,需先將被調用函數參數實參)准備好。然後執行call指令,完成了兩個任務:

1.將調用函數下一條指令入棧,被調函數返回後將取這條指令繼續執行.

2.修改指令指針寄存器eip的值,使其指向被調函數的執行位置.


一個函數被調用,首先要完成以下動作建立新棧幀):

將調用函數的棧幀棧底地址入棧,即將bp寄存器的值壓入調用棧中,將來返回時用。

設置被調函數的棧幀棧底地址ebp=esp。


函數調用和返回過程的規則:該規則與操作系統和編譯器相關)

  • 參數壓棧傳遞,並且是從右向左;

  • ebp總是指向棧幀的棧底;

  • 返回值通過eax寄存器傳遞;

  • 被調用函數棧幀的棧底保存著調用函數棧幀的棧底;

  • 被調用函數棧幀的棧底上一個存儲單元保存著該函數返回後下一條指令的地址。

    232614751.png


用下面的代碼來解釋以上規則。


int bar(int c, int d)

{

int e = c +d;

return e;

}


int foo(int a, int b)

{

return bar(a, b);

}


int main(void)

{

foo(2, 3);

return 0;

}


在編譯時加上-g選項,用objdump反匯編時可以把C代碼和匯編代碼穿插起來顯示,這樣C代碼和匯編代碼的對應關系看得更清楚,這裡只列出我們關心的部分:指令地址每次編譯都不同只作參考。)

080483b4 <bar>:

#include <stdio.h>


int bar(int c, int d)

{

80483b4:55 push %ebp

80483b5:89 e5 mov %esp,%ebp

80483b7:83 ec 10 sub $0x10,%esp

int e = c +d;

80483ba:8b 45 0c mov 0xc(%ebp),%eax

80483bd:8b 55 08 mov 0x8(%ebp),%edx

80483c0:8d 04 02 lea (%edx,%eax,1),%eax

80483c3:89 45 fc mov %eax,-0x4(%ebp)

return e;

80483c6:8b 45 fc mov -0x4(%ebp),%eax

}

80483c9:c9 leave

80483ca:c3 ret


080483cb <foo>:


int foo(int a, int b)

{

80483cb:55 push %ebp

80483cc:89 e5 mov %esp,%ebp

80483ce:83 ec 08 sub $0x8,%esp

return bar(a, b);

80483d1:8b 45 0c mov 0xc(%ebp),%eax

80483d4:89 44 24 04 mov %eax,0x4(%esp)

80483d8:8b 45 08 mov 0x8(%ebp),%eax

80483db:89 04 24 mov %eax,(%esp)

80483de:e8 d1 ff ff ff call 80483b4 <bar>

}

80483e3:c9 leave

80483e4:c3 ret


080483e5 <main>:


int main(void)

{

80483e5:55 push %ebp

80483e6:89 e5 mov %esp,%ebp

80483e8:83 ec 08 sub $0x8,%esp

foo(2, 3);

80483eb:c7 44 24 04 03 00 00 movl $0x3,0x4(%esp)

80483f2:00

80483f3:c7 04 24 02 00 00 00 movl $0x2,(%esp)

80483fa:e8 cc ff ff ff call 80483cb <foo>

return 0;

80483ff:b8 00 00 00 00 mov $0x0,%eax

}



(gdb) info registers bar函數的棧幀)

esp 0xbffff718

ebp 0xbffff728

foo函數的棧幀

esp 0xbffff730

ebp 0xbffff738

main函數的棧幀

esp 0xbffff740

ebp 0xbffff748


140921194.png



以上是函數調用過程中的棧幀框架,現在看如何返回:

bar函數的返回過程:

mov -0x4(%ebp),%eax;將e的值賦給eax。

leave;它是push %ebp和mov %esp,%ebp的逆操作。把ebp的值賦給esp,現在esp的值是0xbffff728,ebp和esp都指向bar的棧底;棧底存放的正是foo函數的ebp。彈出ebp,現在ebp指向了foo函數的棧底,同時 esp加4.esp指向了foo函數的棧頂。foo函數的棧頂存放的是foo 函數中call指令的下一條指令地址80483e3。到此為止,現在已經返回到foo函數的棧幀。

ret;該指令是call指令的逆操作;把esp的值恢復給eip,同時esp加4。修改了程序計數器eip,現在跳轉到 地址80483e3,該處指令是leave ;ret;重復上述過程即返回到main函數。


main函數調用foo,foo調用bar,bar返回到foo,foo返回到main整個調用過程結束。


本文出自 “note” 博客,請務必保留此出處http://gcfred.blog.51cto.com/7948335/1302721

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved