在函数声明/定义中使用 "__weak" 关键字时的注意事项

近期在编写 MAX30102 的驱动,引用了 Github 上 MAX30102_for_STM32_HAL 这个开源项目的代码。

按照项目说明,只需要自定义一个 max30102_plot() 函数,即可实现传感器读取后的回调调用。但博主实际编写程序后,却发现自定义的回调函数并没有被调用成功。

经过排查,博主发现,这一问题是由于开源代码中 __weak 关键字使用不当导致,故在此分享一下正确使用方式。

(提示:请注意区分下文中提到的“函数声明”与“函数定义”这两个概念)

问题分析

我在 main.c 中重写 max30102_plot() 函数如下(函数内容并不重要):

1
2
3
4
5
6
7
8
9
// Override plot function
void max30102_plot(uint32_t ir_sample, uint32_t red_sample)
{
RingBuff_Write(&ringBuffer_ir, ir_sample);
RingBuff_Write(&ringBuffer_red, red_sample);
#if SENSOR_DATA_PRINT
u1_printf("ir,r:%u,%u\n", ir_sample, red_sample); // Print IR and Red
#endif
}

正常来说,MCU 应当执行我这个重写后的函数,但烧录后,博主发现该函数并未按照预期被执行。

故使用调试器观察发现,MCU 执行的仍然是开源代码 max30102_for_stm32_hal.c 中未被覆写的“弱定义”的函数,如图所示:

执行的是弱定义函数

进一步观察发现,其实在编译期间编译器已经给出了一个 warning(注意,似乎有某些版本不警告?):

编译器警告

继续深究,博主发现,在开源代码 max30102_for_stm32_hal.h 这个头文件中,对 max30102_plot() 的声明如下:

1
__weak void max30102_plot(uint32_t ir_sample, uint32_t red_sample);

这个声明含有 __weak 关键字,而博主由于代码需要,在 main.c 中“include”了这个头文件,因此引入的是“弱声明”。

在“弱声明”和“弱定义”都存在的情况下,编译器选择链接了开源代码中“弱声明”的函数,而忽略了博主重写的“非弱声明”函数,并抛出了一个警告作为提示,这就是问题产生的原因。

解决方案

那么正确的使用方式应该是什么样的?

博主想到,在 ST 官方 HAL 库中,有大量中断回调函数是采用 __weak 定义的,我们不妨借鉴它的写法。

这里以 HAL_GPIO_EXTI_Callback 这个函数为例,可见,在 .c 文件里的函数定义中包含 __weak 关键字;但在 .h 文件里的函数声明中并不包含。

源文件包含弱定义
头文件不包含弱声明

查明了问题,解决方案很简单:编辑 max30102_for_stm32_hal.h 这个头文件,移除函数声明前的 __weak 即可。如此一来,“弱声明”变为“非弱声明”,故编译器能够优先查找博主自己重写的“非弱定义”函数,且编译警告消失。

所以,用一句话总结,就是:

仅将 __weak 用于函数定义;在函数声明中,不要使用 __weak 关键字!

当然,后续我也开了个 PR 给原作者:Fix the declaration of max30102_plot(),目前已经 merge,大家可以放心使用。

参考

其它的一些分析:ARM 之十一 __weak 和 __attribute__((weak)) 关键字的使用