[开发工具] C语言在ARM中函数调用时,栈是如何变化的?

[复制链接]
4900|36
 楼主| kmzuaz 发表于 2024-12-30 12:03 | 显示全部楼层 |阅读模式
Arm指令集介绍
崇尚简单粗暴的介绍方式,我们直接来看各个寄存器的大体用法,详细用法可百度,不,谷歌。
1.    r0-r3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。被调用函数在返回之前不必恢复 r0-r3。---如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。2.    r4-r11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。r11 是栈帧指针 fp。3.    r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。4.    寄存器 r13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。5.    寄存器 r14 是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复6.    寄存器 r15 是程序计数器 pc。它不能用于任何其它用途。
演示代码
假如现在你已经掌握了 arm 指令的用法,即便没有掌握也没关系,“书到用时回头翻”。这里以一段简单的 c 语言为例:
代码语言:javascript
复制


#include .h= ;int (,int b{    int c 0= a ;    ;main)= ;    int j 5= (i)return ;编译一下,然后反汇编:
$ arm-linux-gnueabi-gcc main.c -o main $ arm-linux-gnueabi-objdump -D -D main
代码语言:javascript
复制


<fun:   :       e52db004        push    }            (str fp[sp-])   :       e28db000        add     fp, #10408, sp20   1040c, , #1610410, , #20; 10414, #10418, , #8:       e51b2010        ldr     r2[fp-]   :       e51b3014        ldr     r3[fp-]  0xffffffec   :       e0823003        add     r3, r3   :       e50b3008        str     r3[fp-]   1042c, , #810430, r3   :       e24bd000        sub     sp, #10438{fp; , ]4:       e12fff1e        bx      lr<main:   :       e92d4800        push    , lr10444, sp4   :       e24dd008        sub     sp, #:       e3a03004        mov     r34   :       e50b300c        str     r3[fp-]   :       e3a03005        mov     r35   :       e50b3008        str     r3[fp-]   1045c, , #810460, , #121046410400 >   :       e1a02000        mov     r2:       e59f3010        ldr     r3[pc16; <main0x4410470, ]   :       e3a03000        mov     r30   :       e1a00003        mov     r0:       e24bd004        sub     sp, #10480{fp}   :       , r2, lsr #如何能让读者接受吸收的更快,我一直觉得按照学习效率来讲的话顺序应该是视频,图文,文字。反正我是比较喜欢视频类的教学。这里给大家画下栈变化的过程是什么样子的。这里的图是结合上面的代码来画的,希望有助于读者的理解。1.程序在内存分布区域



2.全局变量m赋值



3.保存进入main之前的栈底, fp-sp之间是当前函数栈



4.函数main的栈已经准备好了



5.i入栈



6.j入栈



7.准备函数fun的调用, 形参反向入栈 先形参b入栈



8.形参a入栈



9.留空一个地址作为fun返回值, 待后面返回时填入



10.fun返回地址入栈, 通常是main函数当前pc指针的下一个



11.main函数的栈底地址入栈



12.pc指针跳转fun代码



13.c入栈



14.可以看到函数fun的数据 形参a,b 在上一层函数的栈中. 一部分在自己的栈上. 此步取值到加法器中进行加法运算,再赋值给c



15.c赋给返回值,填入上面的留空位置



16.栈底恢复上一层



17.lr赋值给pc, 实现了跳转



18.返回值赋值给全局变量m



19.前面函数调用的形参已经无用,回滚sp



20.函数返回,清理main的栈空间





gejigeji521 发表于 2024-12-30 19:24 | 显示全部楼层
现在学单片机不用考虑这个了吧
dongnanxibei 发表于 2024-12-30 22:05 来自手机 | 显示全部楼层
从来没有考虑过这个问题。
和下土 发表于 2024-12-30 23:32 | 显示全部楼层
被调用的函数在返回之前不需要恢复这些寄存器的值,除非调用的函数自己需要。
和下土 发表于 2024-12-30 23:33 | 显示全部楼层
我们在 C 代码中嵌入汇编指令时,实际上是直接操作这些寄存器。
申小林一号 发表于 2024-12-31 16:34 | 显示全部楼层
感谢分享,学习一下
ulystronglll 发表于 2025-3-19 12:29 | 显示全部楼层
在调用函数之前,栈的状态如下:
SP(Stack Pointer):指向当前栈顶。
FP(Frame Pointer):指向当前栈帧的基址。
LR(Link Register):保存返回地址。
51xlf 发表于 2025-3-19 13:32 | 显示全部楼层
在被调用函数执行过程中,局部变量会被存储在栈中分配的空间里,函数可以对这些局部变量进行读写操作。同时,函数可能会继续使用栈来进行一些临时数据的存储和操作。
lihuami 发表于 2025-3-19 15:16 | 显示全部楼层
如果函数有参数,参数值会被传递给函数,并可能在栈上进行存储或操作。
maqianqu 发表于 2025-3-19 18:22 | 显示全部楼层
函数内部可能会声明局部变量,这些变量通常存储在栈中。
macpherson 发表于 2025-3-20 09:08 | 显示全部楼层
减少局部变量大小,改用全局变量或动态内存
hilahope 发表于 2025-3-20 09:50 | 显示全部楼层
函数返回前,会将之前压入栈中的寄存器值弹出,恢复到相应的寄存器中。
uptown 发表于 2025-3-20 10:31 | 显示全部楼层
在调用函数之前,需要保存当前的上下文,以便在函数返回时恢复。
dspmana 发表于 2025-3-20 10:46 | 显示全部楼层
函数被调用后,它可能会分配一些局部变量。这些局部变量的空间会在栈帧的底部分配,通常是通过修改栈指针(SP)来实现的。
tifmill 发表于 2025-3-20 11:03 | 显示全部楼层
栈的变化是由编译器根据ATPCS标准生成的汇编代码来控制的。
lzbf 发表于 2025-3-20 11:42 | 显示全部楼层
在C语言中,当使用ARM架构进行函数调用时,栈的变化是一个关键的过程,它涉及到多个寄存器和内存地址的操作。
louliana 发表于 2025-3-20 12:42 | 显示全部楼层
函数返回前,会释放为局部变量分配的栈空间。栈指针(SP)会向上移动,恢复到进入函数时的位置。
elsaflower 发表于 2025-3-20 13:28 | 显示全部楼层
通过栈回溯分析函数调用路径。              
janewood 发表于 2025-3-20 13:45 | 显示全部楼层
函数内部的局部变量会被分配在栈上,通常从栈顶向下分配空间。
mickit 发表于 2025-3-20 14:04 | 显示全部楼层
栈是一种后进先出(LIFO)的数据结构,在 ARM 架构中,栈用于存储函数调用时的局部变量、函数参数、返回地址等信息。栈指针(SP)指向栈顶元素,栈的增长方向通常是从高地址向低地址。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

20

主题

3410

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部