C 语言弱链接函数 __weak

1. 什么是弱链接函数

__weak 修饰的函数属于弱符号,用于提供可被覆盖的默认实现。当链接时发现同名的强符号函数,链接器会优先使用强符号,弱符号被忽略。

ℹ️ 典型应用:HAL 库提供空的弱回调函数,用户在自己的代码中实现同名函数即可覆盖,无需修改库代码。

2. 语法格式

GCC 原生语法是 __attribute__((weak)),通常会自定义宏 __weak 来简化使用:

#define __weak __attribute__((weak))

__weak void function_name(void) {
    // 默认实现
}
💡 编译器差异

• ARM GCC / Keil MDK 内置支持 __weak

• 标准 GCC 需要自己定义宏 #define __weak __attribute__((weak))

3. 基础示例

00weak_default.c - 提供默认实现

#include <stdio.h>

#define __weak __attribute__((weak))

// 弱符号:默认配置
__weak void config(void) {
    printf("默认配置\n");
}

00weak_user.c - 用户覆盖实现

#include <stdio.h>

// 强符号:覆盖默认配置
void config(void) {
    printf("配置一\n");
}

int main(void) {
    config();  // 输出:配置一
    return 0;
}

编译运行

gcc 00weak_default.c 00weak_user.c -o test
./test
# 输出:配置一

4. 工作原理

弱链接函数工作流程

4.1 编译阶段:打标记

  • 编译器看到 __weak 时,给符号打上「弱标记」
  • 同一个 .c 文件内不能有多个同名函数(无论是否 weak),否则报重定义错误
  • 此阶段不处理覆盖逻辑

4.2 链接阶段:看标记

  • 链接器识别符号的弱标记(用 nm 命令可看到 W 标记)
  • 按规则处理:强符号 > 弱符号
  • 多个强符号会报重定义错误

链接阶段符号处理

📋 核心机制

「编译打标记,链接看标记;weak 的作用全在链接阶段」

5. 嵌入式示例:STM32 GPIO 中断回调

stm32f4xx_hal_gpio.c - HAL 库提供弱实现

// HAL 库提供空的弱回调函数
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    UNUSED(GPIO_Pin);
    // 用户应该在自己的代码中实现此函数
}

// GPIO 中断服务函数(HAL库内部)
void EXTI0_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
    // 内部会调用 HAL_GPIO_EXTI_Callback
}

main.c - 用户实现强符号

// 用户实现 GPIO 中断回调(强符号覆盖弱符号)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if(GPIO_Pin == GPIO_PIN_0) {
        // 按键中断处理
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  // LED 翻转
    }
}

结果:链接时使用 main.c 中的强符号,HAL 库的弱符号被忽略。


6. 注意事项

6.1 ❌ 同文件内定义多个同名函数

// error.c
__weak void func(void) { }
void func(void) { }  // ❌ 编译错误:重定义
⚠️ 注意:这不是 weak 失效,而是 C 语言基础规则——单文件不允许同名函数。

6.2 ❌ 函数签名不匹配

// lib.c
__weak void Init(void) { }

// main.c
void Init(int param) { }  // ❌ 签名不同,链接时报错
⚠️ 注意:C 语言不支持函数重载,同名不同参数的函数会导致符号冲突。即使一个是弱符号,链接器也无法正确处理,会报错或产生未定义行为。

6.3 ✅ 必须在不同源文件

// lib.c
__weak void Init(void) { printf("default\n"); }

// main.c
void Init(void) { printf("custom\n"); }  // ✅ 正确覆盖

7. 总结

📋 速记要点
  • __weak 用于提供可覆盖的默认实现
  • 必须在不同 .c 文件中才能覆盖
  • 函数签名必须完全一致
  • 常用于 HAL 库回调、RTOS 钩子函数、驱动框架