C语言函数不同个数、大小形参对执行速度的影响:以Cortex-M3为例从汇编角度分析原因

0 资料&工具

c 复制代码
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)

示例程序:

c 复制代码
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

示例程序:

c 复制代码
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,看看是否同样需要使用堆栈传递形参。对应代码如下:

c 复制代码
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个

示例程序:

c 复制代码
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个时,建议使用指针传递参数。

相关推荐
时光飞逝的日子2 个月前
ThreadX源码:Cortex-A7的tx_thread_irq_nesting_start(嵌套中断开始动作).s汇编代码分析
threadx·armv7·cortex-a7·arm内核·嵌套中断
时光飞逝的日子2 个月前
C语言调用子函数时入/出栈(保护/恢复现场)全过程分析:以Cortex-M3为例
arm·cortex-m3·出栈·入栈·保护现场·恢复现场
倒霉熊dd2 个月前
Cortex-M3架构学习
学习·嵌入式·cortex-m3
Spectre_Mercury5 个月前
基于cortex-M3的rtos原理(上)
freertos·cortex-m3·rtos
桃子丫5 个月前
基于Cortex的MCU设计
arm开发·单片机·嵌入式硬件·mcu·cortex-m3
CodingCos1 年前
【ARM Cortex-M 系列 1 -- Cortex-M0, M3, M4, M7, M33 差异】
arm开发·cortex-m7·cortex-m33·cortex-m3·cortex-m4·cortex-m0·cortex-m23