关于在ARM中的函数调用问题

本人菜鸟一只,最近在学习arm汇编,做一个关于函数调用的笔记,如果有出错的地方希望大牛们指出。

以下汇编代码均为MDK5所生成的
首先看一个简单的例子

int main(void)
{				 
	int a=1;
    int b=2;
} 

这个函数翻译成汇编的执行过程为

 movs r4,#0x01 ; int a=1;
 movs r5,#0x02 ; int b=2;  

此时可以看出对于a和b的值只是简单的用寄存器保存了一下
然后在看如果用 valatile 修饰一下会怎么样

int main(void)
{				 
		valatile int a=1;
	    valatile int b=2;
}  

汇编执行过程如下:

movs r0,#0x01
str r0,[sp,#0x04] ;valatile int a=1;
movs r0,#0x02
str r0,[sp,#0x00] ;valatile int b=2;

此时可以看到已经不只是把a和b简单的用寄存器保存起来了,还对a和b进行了压栈操作。

再来看一下如果用 static 修饰一下会怎么样:

int main(void)
{				 
		static int a=1;
	    static int b=2;
} 

经过测试,此时寄存器并没有保存a和b的值,原因是用static 修饰后变量为静态局部变量,不保存在堆区。关于代码区,全局数据区,堆区,栈区 下面做一点简单的介绍。
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放 在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。关于更详细的的介绍可以参考下面的文章。
C++的static关键字及变量存储位置总结

然后了解完这些我们再来看具体调用函数时是怎么操作的,先来一个简单的例子

int f(int a1,int a2)
{
	return a1+a2;
}
int main(void)
{				 
	int a=f(1,2);
} 

其中在main函数中的汇编代码如下

push {lr} ;
movs r1,#0x02 
movs r0,#0x01 ;从右向左传递形参,寄存器从大到小
bl.w f(0x080002f2) ;0x080002f2为函数f的地址
mov r3,r0 ;一般将r0作为函数的返回值

再来看f函数的汇编代码

mov r2,r0;将r0中的值传递给r2,r0要作为返回值返回结果
adds r0,r2,r1 ;通过r0作为返回值,r2,r1中保存了a1,a2的值

这个函数相对来说较为简单,首先在main函数中把要调用的f函数的参数用寄存器保存下来,然后在调用跳转指令跳转到f函数的入口处。关于将参数传递进被调函数中的操作这里要做一点说明,当参数小于四个时传递参数是用寄存器保存下来的,并且参数是从左往右,寄存器是从大到小使用的,当参数大于四个时多余的参数需要通过压栈的方式传进被调函数,不得不说这样会使函数执行的效率大大降低(因为要进行压栈操作)。
然后来看一个参数为五个参数时函数调用时的具体执行过程:

int f(int a1,int a2,int a3,int a4,int a5)
{
	return a1+a2+a2+a3+a4+a5;
}
int main(void)
{				 
		int a=f(1,2,3,4,5);
} 

main函数汇编如下:

push {r3-r4,lr}
movs r0,#0x05
movs r3,#0x04
movs r2,#0x03
movs r1,#0x02
str r0,[sp,#0x00] ;寄存器不够,此处要借助压栈来保存参数
movs r0,#0x01
bl.w f(0x080002f2) ;0x080002f2为函数f的地址
mov r4,r0 ;r0作为返回值

f函数的汇编代码如下:

push {r4-r5,lr} ;此处要将f中会用到的寄存器压栈保护,lr为函数返回的地址
mov r4,r0 ;r0要用作返回值
ldr r5,[sp,#0x0c] ;因为压栈时压入了三个寄存器,并且在arm中栈是向下生长的,所以需要加上0x0c来找到之前压入的函数形参
adds r0,r4,r1
add r0,r0,r2
add r0,r0,r3
add r0,r0,r5

经过分析可以发现在main中进行参数传递时由于参数大于四个,在传递第五个参数时通过压栈操传递,并且在f中又对第五个参数进行了出栈操作。

若要在f中定义变量则需要用到其他寄存器来保存变量的值,但是变量定义太多的话,需要进行压栈来保存变量的值,更改后的f函数如下

int f(int a1,int a2)
{
	int a=1;
	int b=2;
	int c=3;
	int d=4;
	int e=5;
	int f=6;
	int g=7;
	int h=8;
	int i=9;
	int j=10;
	int k=11;
	int m=12;
	return a1+a2;
}
int main(void)
{				 
	int a=f(1,2);
} 

f函数汇编代码如下:

push {r3-r11,lr};
move r2,r0 ;r0用作函数返回值
moves r3,#0x01 ;int a=1
moves r4,#0x02
moves r5,#0x03
moves r6,#0x04
moves r7,#0x05
moves r12,#0x06
moves r8,#0x07
moves r9,#0x08
moves r10,#0x09
moves r11,#0x0a
moves rlr,#0x0b
moves r0,#0x0c ;m=12
str r0,[sp,#0x00] ;寄存器不够,此时需要进行压栈操作
adds r0,r2,r1 ;通过r0作为返回值,r2,r1中保存了a1,a2的值

以上就是我对函数调用的理解,其中还有以下问题不太明白,希望有明白的大牛可以指教一二。
1、在f函数的开始处进行了压栈,为什没没有出栈的语句?
2、在函数调用过程中参数小于四个时是自动压栈的,这个是怎么体现出来的?

下面附上学习过程中参考的几篇文章
ARM架构下函数调用过程分析
硬件堆栈和软件堆栈
ARM上函数调用参数超过四个的时传递方法
C函数调用过程原理及栈帧分析

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值