时间:2025-05-08
本人笔记均是根据自己多年从业的理解整理而成,转载请备注来源。
目录
“内联函数(inline-function)”是C语言(C99起正式引入)和C++中一个重要的编译器优化手段,在语法中是通过关键字的形式出现的,即在函数前添加“inline”关键字。内联函数在嵌入式开发中尤其关键,STM32中的大量库函数就广泛使用了inline关键字来提高代码运行效率、减少上下文切换开销。
为方便读者更好的理解内联函数,我们依旧以最经典的STM32为例来讲。本文将从定义、工作原理、优缺点、应用场景、实际代码例子这几个角度来全面理解内联函数。
1. 什么叫做内联函数(inline function)?
内联函数是一种提示编译器将函数“展开”为代码片段,而不是进行正常函数调用的机制,所谓“正常的函数调用”,就是指代码在运行至函数时,会进行压栈、跳转、返回,而内联函数则是在函数被调用的位置上直接插入函数体代码,相当于用代码段以及传入的参数完整的替换掉了函数调用语句。
2. 内联函数和普通函数的区别是什么?
从实现方式上来讲,普通函数是采用压栈、跳转、返回机制实现函数功能,而内联函数则是展开替换成代码片段;
从开销上来说,普通函数有函数跳转、压栈、出栈开销,内联函数无调用开销,运行速度更快,所以我们在之前说inline可以提高代码运行效率、减少上下文切换开销。
但内联函数也有缺点,主要是在可调试性与可维护性上,内联函数展开后无法单步跳转到原函数,所以会带来一些调试上的问题,并且内联函数大量内联可能增加代码大小,导致函数体积过大。
3. STM32中为什么使用内联函数而不是宏?
这个问题我相信也会有小白想到,我们为什么不用宏替换呢?而非要定义一个内联函数出来?事实上,虽然宏也能实现代码替换,但宏不做类型检查,容易出错,且宏无法调试,不支持断点,宏也不具备函数语义,无法递归、嵌套、做返回值处理。因此,内联函数是性能与安全性之间的最佳平衡点,比宏更强,运行效率也几乎与宏一样快。
4. 以GPIO输出为例讲讲二者区别
(1)普通函数实现(非内联):
void GPIO_SetPin(GPIO_TypeDef* port, uint16_t pin)
{
port->BSRR = pin;
}
调用会触发压栈、跳转、返回,有一定开销
(2)内联函数实现:
__STATIC_INLINE void GPIO_SetPin(GPIO_TypeDef* port, uint16_t pin)
{
port->BSRR = pin;
}
被调用时直接插入函数体:
GPIOx->BSRR = pin;
没有任何函数调用开销,执行速度极快
5. 何时使用inline?
当你在项目中遇到:
(1)某个寄存器写操作需要频繁执行
(2)宏不够安全,但函数又太重
(3)代码重复调用一个短逻辑
可以这样写:
static inline void Toggle_LED(void)
{
GPIOC->ODR ^= GPIO_Pin_13;
}
这样就能安全地、快速地控制LED闪烁,而不用担心效率问题。
这里补充一下,在第6部分,我们提到了“宏不够安全,但函数又太重”这一说法,有读者不太理解什么意思,我来重点说一下。
所谓“宏不安全”是指,宏本质是简单的文本替换,没有类型、作用域、语法检查,宏在预处理阶段只是被简单的展开,宏不理解何谓“类型”,也不知道用户传的是表达式、变量还是指针,因此我们说“宏”是不安全的。
所谓“函数重”是指执行一次函数调用需要较大系统开销,在嵌入式系统中,尤其是STM32这种资源有限的MCU,函数调用会有以下开销:
(1)压栈:保存返回地址、参数、寄存器状态
(2)跳转到函数地址:PC(程序计数器)跳到函数体处执行
(3)执行函数内容:正常逻辑执行
(4)恢复上下文与出栈:回到原来位置继续执行
即使函数只有一行代码,也会完整走这一套过程,在高频调用或时间敏感场景中,这种“系统性成本”显得太高。所以我们说,宏不够安全,用内联函数,普通函数太重,所以也用内联函数,这是内联函数在STM32嵌入式开发中被频繁使用的真正原因。