头文件中的static inline函数
/**
* @brief Enable AHB1 peripherals clock.
* @rmtoll AHBENR DMA1EN LL_AHB1_GRP1_EnableClock\n
* AHBENR FLASHEN LL_AHB1_GRP1_EnableClock\n
* AHBENR CRCEN LL_AHB1_GRP1_EnableClock
* @param Periphs This parameter can be a combination of the following values:
* @arg @ref LL_AHB1_GRP1_PERIPH_DMA1
* @arg @ref LL_AHB1_GRP1_PERIPH_FLASH
* @arg @ref LL_AHB1_GRP1_PERIPH_CRC
* @retval None
*/
__STATIC_INLINE void LL_AHB1_GRP1_EnableClock(uint32_t Periphs)
{
__IO uint32_t tmpreg;
SET_BIT(RCC->AHBENR, Periphs);
/* Delay after an RCC peripheral clock enabling */
tmpreg = READ_BIT(RCC->AHBENR, Periphs);
(void)tmpreg;
}在阅读STM32官方示例代码(如开头所示)时,经常可以在HAL函数库的.h头文件中看到static inline开关的函数。为什么要使用static inline定义函数?它这样定义的方式又是在解决什么问题?在上C语言课程时,老师讲过,要将函数实现放在.c的文件中,而仅在.h头文件中做外部链接声明,以供外部.c文件引用。这时两者冲突了!是老师教的不对,还是ST官方代码有问题?让我们一点一点分析来看。
inline关键字修饰函数
inline关键字的作用是向编译器发出的建议,希望将函数体直接展开到调用处,以消除函数调用的开销(如压栈、跳转、返回等指令)。那为什么又必须在头文件中定义呢?
编译器在进行编译时,是以单个 .c 文件为单位进行的。如果函数声明在头文件,而实现在另一个 .c 文件中,当前编译单元看不到函数体,就无法进行内联展开,只能生成普通的函数调用指令。
只有将函数定义(实现)放在头文件中,所有包含该头文件的源文件才能在编译时看到函数体,编译器才有机会将其内联展开。
static关键字修饰函数
添加static关键字主要是为了避免链接时”多重定义“错误。
如果在头文件中定义一个普通函数(未添加static声明),当多个 .c 源文件包含该头文件时,每个 .c文件 都会生成一份该函数的目标代码。在链接阶段,链接器会发现多个同名符号,从而报错“多重定义”(Multiple Definition Error)。而static关键字赋予函数内部链接(Internal Linkage)属性。这意味着该函数仅对当前编译单元(即当前的 .c 文件及其包含的头文件)可见。详细点说,就是: 当多个 .c 文件包含含有 static inline 函数的头文件时,每个 .c 文件都会拥有该函数的一个独立私有副本。 由于这些副本彼此不可见,链接器不会认为它们是冲突的重复定义,从而顺利链接。
static与inline组合
inline关键字仅仅是建议编译器做内联处理,而加上static后,语义变得明确且稳健:“这个函数是每个文件私有的,并且建议内联”。即使编译器决定不内联(例如优化级别低或函数过大),它也会在每个使用该函数的 .o 文件中生成一个静态函数副本,保证程序能正确运行且无链接错误。
static inline的适用条件
上面的描述已经清晰表明,每个.c文件在编译的时候都会将该生成一个静态函数副本,而不是传统函数的描述符,而这也就导致了代码膨胀的问题。即,如果该函数未被内联且被频繁引用,或者头文件被大量源文件包含,会导致最终生成的bin文件体积极具增大。
static inline的适用条件:
此模式仅适用于短小、高频调用的工具函数,比如ST示例代码里面的寄存器操作;
替代部分宏定义的安全方案。宏只是文本替换,容易引发类型错误,也不方便单步跟踪调试;
总结
在 C 语言头文件中使用 static inline定义函数,主要是为了解决代码复用、性能优化与链接冲突三者之间的矛盾。这是一种在嵌入式开发、操作系统内核(如 Linux Kernel)及高性能库中非常常见的最佳实践。
老师课本所教知识没有错误,课本知识具有普适性,而大厂的代码是在特定需求,特定环境下的优化下的专门处理。是知识应用的一种升华。
通过源代码学习知识,关注我!我是你们的老朋友jobs。
我要赚赏金
