函數調用時棧在做什么?


 以一段簡單的函數調用開始,看看程序的上下文是如何切換的。

工具:Keil5

平台:Cortex-M7

1. 簡單函數調用

 1 int func(int a, int b, int c, int d, int e, int f)
 2 {
 3     int buf[10], i;
 4     
 5     buf[0] = 0;
 6     i = a + b + c + d + e + f;
 7     
 8     if (i > 0)    
 9     {        
10         buf[0] = 1;
11     }
12     return i + buf[0];
13 }
14 
15 int main()
16 {
17     int value = func(5, 6, 7, 8, 9, 10);
18     
19     return value;
20 }

 

 

編譯成功后仿真,看看匯編里做了啥:

1. 程序執行到C代碼的17行 時調用子函數,准備切換下文,首先將func()的傳參從右向左掃描,依次暫存在寄存器中(見下列匯編 1-7行)。由於傳參數多達6個,而寄存器R0-R3不夠用(備注1),只好將末尾的兩個傳參壓入棧中(棧指針會偏移8個字節,備注2)。

1 0x080002E8 200A      MOVS          r0,#0x0A
2 0x080002EA 2109      MOVS          r1,#0x09
3 0x080002EC 2308      MOVS          r3,#0x08
4 0x080002EE 2207      MOVS          r2,#0x07
5 0x080002F0 E9CD1000  STRD          r1,r0,[sp,#0]
6 0x080002F4 2106      MOVS          r1,#0x06
7 0x080002F6 2005      MOVS          r0,#0x05
8 0x080002F8 F7FFFFE1  BL.W          func (0x080002BE)

 

 

 2. 進入func函數內,首先將上文的R4-R7寄存器及 lr 鏈接地址壓入棧中(備注3),然后繼續准備下文。

      首先為數組buf開辟內存空間,大小為sp棧指針偏移0x28=40字節。

      接下來要將傳參准備好,由於有兩個傳參在棧中,需要透過sp指針取出,見下列匯編 第7行,此處對sp的指針偏移了0x3C=60字節,剛好就是(0x28=40  + (R4-R7)*4 =16  +  lr*4=4)的長度,偏移回了傳參壓棧時的位置。

   匯編9-10行要對buf[0]賦0操作,由於buf剛在棧中分配完,且棧的生長方向向下,因此sp此時指向的地址為buf[10]的最低位,即buf[0],STR指令的sp偏移即為0x00.

   繼續往下看,r1-r4,r6-r7保存了6個傳參值,求和的結果最終存儲在r5中

   后續對sp及r0、r5的操作大同小異,不再贅述。

 1     57: { 
 2     58:         int buf[10], i; 
 3     59:          
 4 0x080002BE B5F0      PUSH          {r4-r7,lr}
 5 0x080002C0 B08A      SUB           sp,sp,#0x28
 6 0x080002C2 4604      MOV           r4,r0
 7 0x080002C4 E9DD670F  LDRD          r6,r7,[sp,#0x3C]
 8     60:         buf[0] = 0; 
 9 0x080002C8 2000      MOVS          r0,#0x00
10 0x080002CA 9000      STR           r0,[sp,#0x00]
11     61:         i = a + b + c + d + e + f; 
12     62:          
13 0x080002CC 1860      ADDS          r0,r4,r1
14 0x080002CE 4410      ADD           r0,r0,r2
15 0x080002D0 4418      ADD           r0,r0,r3
16 0x080002D2 4430      ADD           r0,r0,r6
17 0x080002D4 19C5      ADDS          r5,r0,r7
18     63:         if (i > 0)       
19     64:         {               
20 0x080002D6 2D00      CMP           r5,#0x00
21 0x080002D8 DD01      BLE           0x080002DE
22     65:                 buf[0] = 1; 
23     66:         } 
24 0x080002DA 2001      MOVS          r0,#0x01
25 0x080002DC 9000      STR           r0,[sp,#0x00]
26     67:         return i + buf[0]; 
27 0x080002DE 9800      LDR           r0,[sp,#0x00]
28 0x080002E0 4428      ADD           r0,r0,r5
29     68: } 

 

思考

備注1. 為什么傳參只用到r0-r3寄存器?

經搜索,在《The ARM-THUMB Procedure Call Standard》手冊上對寄存器的使用做了如下限定:

備注2. 什么時候sp指針會移動?

 特殊的指令在執行時SP指針會移動,例如:PUSH/POP  LDR/STR 等。

 

備注3. r4-r10保持上文的信息,每次入棧的寄存器數為何不一樣?

 根據本文例子1/2的觀察看,不是每次都會將所有的臨時寄存器r4-r11入棧,而是要看上文環境所使用到的寄存器個數而定。

 

2. 函數遞歸調用

若將func() 函數改造為遞歸調用,則棧的情況又如何呢?

 1 int func(int a, int b, int c, int d, int e, int f)
 2 {
 3     int buf[10], i;
 4     
 5     buf[0] = 0;
 6     i = a + b + c + d + e + f;
 7     
 8     if (i > 0)    
 9     {        
10         buf[0] = func(a-1, b-1, c-1, d-1, e-1, f-1);
11     }
12     return i + buf[0];
13 }
14 
15 int main()
16 {
17     int value = func(5, 6, 7, 8, 9, 10);
18     
19     return value;
20 }

 

與章節1相比,只是改動了第10行,編譯下看看效果。

1. 首先是傳參的掃描處理,跟章節1完全一樣

1 0x0800030C 200A      MOVS          r0,#0x0A
2 0x0800030E 2109      MOVS          r1,#0x09
3 0x08000310 2308      MOVS          r3,#0x08
4 0x08000312 2207      MOVS          r2,#0x07
5 0x08000314 E9CD1000  STRD          r1,r0,[sp,#0]
6 0x08000318 2106      MOVS          r1,#0x06
7 0x0800031A 2005      MOVS          r0,#0x05
8 0x0800031C F7FFFFCF  BL.W          func (0x080002BE)

 

2. 進入函數內,首先不同的是壓棧的指針數,由之前的 r4-r7 增加到 r4-r10,

    sp指針的偏移也由0x28增加到0x30,多出8個字節

 匯編第6行-20行為buf[0]及i的初始化,執行過程沒有變化,除了采用了不同的寄存器組

 在if語句中,改成遞歸后的變化為:因為傳參眾多,此時寄存器r0-r10都被占用(r10存着 i的臨時值),這就是為什么在遞歸調用本函數之前,入棧的寄存器增加到r10

 此外還記得章節1中寄存器不足,需要壓棧兩個傳參嗎,這兩個傳參剛好放在sp多分配出來的8個字節空間里,其它的傳參依然還是在r0-r3中。

 最終遞歸調用的上下文環境與第一次調用時保持了一致。

  

 1     58: { 
 2     59:         int buf[10], i; 
 3     60:          
 4 0x080002BE E92D47F0  PUSH          {r4-r10,lr}
 5 0x080002C2 B08C      SUB           sp,sp,#0x30  6 0x080002C4 4604      MOV           r4,r0
 7 0x080002C6 460D      MOV           r5,r1
 8 0x080002C8 4616      MOV           r6,r2
 9 0x080002CA 461F      MOV           r7,r3
10 0x080002CC E9DD8914  LDRD          r8,r9,[sp,#0x50]
11     61:         buf[0] = 0; 
12 0x080002D0 2000      MOVS          r0,#0x00
13 0x080002D2 9002      STR           r0,[sp,#0x08]
14     62:         i = a + b + c + d + e + f; 
15     63:          
16 0x080002D4 1960      ADDS          r0,r4,r5
17 0x080002D6 4430      ADD           r0,r0,r6
18 0x080002D8 4438      ADD           r0,r0,r7
19 0x080002DA 4440      ADD           r0,r0,r8
20 0x080002DC EB000A09  ADD           r10,r0,r9
21     64:         if (i > 0)       
22     65:         {               
23 0x080002E0 F1BA0F00  CMP           r10,#0x00
24 0x080002E4 DD0C      BLE           0x08000300
25     66:                 buf[0] = func(a-1, b-1, c-1, d-1, e-1, f-1); 
26     67:         } 
27 0x080002E6 F1A90101  SUB           r1,r9,#0x01
28 0x080002EA F1A80001  SUB           r0,r8,#0x01
29 0x080002EE 1E7B      SUBS          r3,r7,#1
30 0x080002F0 1E72      SUBS          r2,r6,#1
31 0x080002F2 E9CD0100  STRD          r0,r1,[sp,#0]
32 0x080002F6 1E69      SUBS          r1,r5,#1
33 0x080002F8 1E60      SUBS          r0,r4,#1
34 0x080002FA F7FFFFE0  BL.W          func (0x080002BE)
35 0x080002FE 9002      STR           r0,[sp,#0x08]
36     68:         return i + buf[0]; 
37 0x08000300 9802      LDR           r0,[sp,#0x08]
38 0x08000302 4450      ADD           r0,r0,r10
39     69: } 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM