函數調用是通過棧來實現的,而且知道在棧中存放著該函數的局部變量。但是,對于棧的實現細節可能不一定清楚。本文將介紹一下在Linux平臺下函數棧是如何實現的。
棧幀的結構
函數在調用的時候都是在棧空間上開辟一段空間以供函數使用,棧是由高地址向地地址的方向生長的,而且棧有其棧頂和棧底,入棧出棧的地方就叫做棧頂。
在x86系統的CPU中,rsp是棧指針寄存器,這個寄存器中存儲著棧頂的地址。rbp中存儲著棧底的地址。函數棧空間主要是由這兩個寄存器來確定的。
當程序運行時,棧指針rsp可以移動,棧指針和幀指針rbp一次只能存儲一個地址,所以,任何時候,這一對指針指向的是同一個函數的棧幀結構。
而幀指針rbp是不移動的,訪問棧中的元素可以用-4(%rbp)或者8(%rbp)訪問%rbp指針下面或者上面的元素。
測試代碼如下:
#include int sum (int a,int b) { int c = a + b; return c; } int main() { int x = 5,y = 10,z = 0; z = sum(x,y); printf("%drn",z); return 0; }
0000000000000000 : 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 89 7d ec mov %edi,-0x14(%rbp) # 參數傳遞 7: 89 75 e8 mov %esi,-0x18(%rbp) # 參數傳遞 a: 8b 55 ec mov -0x14(%rbp),%edx d: 8b 45 e8 mov -0x18(%rbp),%eax 10: 01 d0 add %edx,%eax 12: 89 45 fc mov %eax,-0x4(%rbp) # 局部變量 15: 8b 45 fc mov -0x4(%rbp),%eax # 存儲結果 18: 5d pop %rbp 19: c3 retq 000000000000001a : 1a: 55 push %rbp # 保存%rbp。rbp,棧底的地址 1b: 48 89 e5 mov %rsp,%rbp # 設置新的棧指針。rsp 棧指針,指向棧頂的地址 1e: 48 83 ec 10 sub $0x10,%rsp # 分配 16字節棧空間。%rsp = %rsp-16 22: c7 45 f4 05 00 00 00 movl $0x5,-0xc(%rbp) # 賦值 29: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%rbp) # 賦值 30: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) # 賦值 37: 8b 55 f8 mov -0x8(%rbp),%edx 3a: 8b 45 f4 mov -0xc(%rbp),%eax 3d: 89 d6 mov %edx,%esi # 參數傳遞 ,從右向左 3f: 89 c7 mov %eax,%edi # 參數傳遞 41: e8 00 00 00 00 callq 46 # 調用sum 46: 89 45 fc mov %eax,-0x4(%rbp) 49: 8b 45 fc mov -0x4(%rbp),%eax # 存儲計算結果 4c: 89 c6 mov %eax,%esi 4e: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 55 55: b8 00 00 00 00 mov $0x0,%eax 5a: e8 00 00 00 00 callq 5f 5f: b8 00 00 00 00 mov $0x0,%eax 64: c9 leaveq 65: c3 retq +0x45>+0x3b>+0x2c>
在函數被調用之前,調用者會為調用函數做準備。首先,函數棧上開辟了16字節的空間,存儲定義的3個int型變量,建立了main函數的棧。
CALL指令內部其實還暗含了一個將返回地址(即CALL指令下一條指令的地址)壓棧的動作(由硬件完成)。
具體來說,call指令執行時,先把下一條指令的地址入棧,再跳轉到對應函數執行的起始處。
審核編輯:湯梓紅
-
嵌入式系統
+關注
關注
41文章
3593瀏覽量
129521 -
Linux
+關注
關注
87文章
11312瀏覽量
209702 -
函數
+關注
關注
3文章
4333瀏覽量
62687
發布評論請先 登錄
相關推薦
評論