0 资料&工具
Cortex M3权威指南(中文).pdf
keil5(用于仿真查看汇编代码、栈变化)
1 C语言函数不同个数、大小形参对执行速度的影响:以Cortex-M3为例从汇编角度分析原因
C语言中有条不成文的规定:不建议函数的形参数量超过4个。为什么会有这样的规定呢,本文以Cortex-M3为例,分析C语言函数不同个数、大小形参对执行速度的影响。
1.1 理论分析
ARM架构的处理器使用R0-R3寄存器传递函数参数,假如有4个参数,则按照顺序依次将参数4写入R3、参数3写入R2、参数2写入R1、参数1写入R0。这是因为访问寄存器的速度要比访问内存(堆栈)要快。如果参数数量超过4个,或者参数太大不足以通过4个寄存器传递参数,则超出部分的参数将通过堆栈传递。使用堆栈传递参数时,参数按照从右往左的顺序压入堆栈,即第一个参数(超出寄存器的部分)会先被压入堆栈。
1.2 举例分析
1.2.1 函数参数个数不超过4个且参数大小均小于寄存器大小(32bit)
示例程序:
typedef unsigned long long int u64;
typedef unsigned int u32;
u32 fun1(u32 p1, u32 p2, u32 p3, u32 p4)
{
return p1 + p2 + p3 + p4;
}
u32 fun2(u32 p1, u32 p2, u32 p3, u32 p4, u32 p5)
{
return p1 + p2 + p3 + p4 + p5;
}
u64 fun3(u64 p1, u64 p2, u64 p3)
{
return p1 + p2 + p3;
}
/**
* 主函数
*/
int main(void)
{
fun1(1, 2, 3, 4);
}
对应的汇编代码:
在跳转fun1函数前依次将参数4-1压入R3-R0,没有使用到堆栈。
1.2.2 函数参数个数超过4个但总大小不超过4x32bit
示例程序:
typedef unsigned long long int u64;
typedef unsigned int u32;
u32 fun1(u32 p1, u32 p2, u32 p3, u32 p4)
{
return p1 + p2 + p3 + p4;
}
u32 fun2(u32 p1, u32 p2, u32 p3, u32 p4, u32 p5)
{
return p1 + p2 + p3 + p4 + p5;
}
u64 fun3(u64 p1, u64 p2, u64 p3)
{
return p1 + p2 + p3;
}
/**
* 主函数
*/
int main(void)
{
fun2(1, 2, 3, 4, 5);
}
对应的汇编代码:
这里关注一下进入mian函数的压栈指令(PUSH),PUSH之后堆栈指针的值为0x200003F8
操作如下:
(1)将参数5保存到R0
(2)将参数4保存到R3
(3)将参数3保存到R2
(4)将参数2保存到R1
(4)将参数5的值保存到堆栈
(5)将参数1保存到R0
我们再将函数参数大小fun2修改为u16,这样下来函数参数总大小为5x16=80bit没超过4x32=128bit,看看是否同样需要使用堆栈传递形参。对应代码如下:
typedef unsigned long long int u64;
typedef unsigned short int u16;
typedef unsigned int u32;
u32 fun1(u16 p1, u16 p2, u16 p3, u16 p4)
{
return p1 + p2 + p3 + p4;
}
u32 fun2(u16 p1, u16 p2, u16 p3, u16 p4, u16 p5)
{
return p1 + p2 + p3 + p4 + p5;
}
u64 fun3(u64 p1, u64 p2, u64 p3)
{
return p1 + p2 + p3;
}
/**
* 主函数
*/
int main(void)
{
fun2(1, 2, 3, 4, 5);
}
对应的汇编代码如下:
可以看到同样需要使用到堆栈传递参数。
1.2.3 函数参数大小超过4x32bit但参数个数不超过4个
示例程序:
typedef unsigned long long int u64;
typedef unsigned int u32;
u32 fun1(u32 p1, u32 p2, u32 p3, u32 p4)
{
return p1 + p2 + p3 + p4;
}
u32 fun2(u32 p1, u32 p2, u32 p3, u32 p4, u32 p5)
{
return p1 + p2 + p3 + p4 + p5;
}
u64 fun3(u64 p1, u64 p2, u64 p3)
{
return p1 + p2 + p3;
}
/**
* 主函数
*/
int main(void)
{
fun3(0x1ffffffff, 0x2ffffffff, 0x3ffffffff);
}
对应的汇编代码:
这里关注一下进入mian函数的压栈指令(PUSH),PUSH之后堆栈指针的值为0x200003F4
操作如下:
(1)将R1设置为0xffffffff
(2)将R0设置为0x03
(3)将R2的值设置为0xffffffff
(4)将R3的值设置为0x02
(5)将R0和R1依次压入堆栈
(6)将R0设置为0xffffffff
(7)将R1设置为0x1
此时R0、R1组成参数1:0x1ffffffff,R2、R3组成参数2:0x2fffffff,加上堆栈中的0x3ffffff便组成了函数的3个参数值。
2 结论
(1)当函数形参数量大于4个或函数形参总大小超过4x32bit(R0-R3寄存器总大小)则会使用堆栈来传递形参,降低函数执行效率。
(2)当我们调用的函数形参数量超过4个时,建议使用指针传递参数。