Stubbing场景下是否存在__attribute__((weak))的反向等效实现?
问题背景与现有实现
现有代码文件
foo_bar_run.c
#include "foo_bar_run.h" int __attribute__((weak)) foo() { printf("foo\n"); } int bar() { printf("bar\n"); foo(); }
foo_bar_run.h
#include <stdio.h> extern int __attribute__((weak)) foo() ; extern int bar();
another_main.c
#include "foo_bar_run.h" int main(void) { bar(); }
bar_test.c
#include <stdio.h> #include "foo_bar_run.h" int foo() { printf("stubbed foo\n"); } int main(void) { bar(); }
现有编译执行效果
生成main_run程序
编译命令:gcc another_main.c foo_bar_run.c -o main_run && ./main_run
输出:
bar foo
生成main_test程序
编译命令:gcc bar_test.c foo_bar_run.c -o main_test && ./main_test
输出:
bar stubbed foo
需求说明
不修改foo_bar_run中foo的函数原型(保持int foo()定义),寻找更优雅的实现方案,可支持测试代码覆盖默认foo实现,可选完全不使用__attribute__关键字。
解决方案
你当前基于弱符号的实现本身就是C语言实现「默认实现+可打桩替换」的标准方案,已经满足要求:C语言默认全局函数就是强符号,测试代码里的foo会自动覆盖foo_bar_run中的弱符号foo,不需要额外添加属性,也不需要修改原有业务代码。
如果不想使用__attribute__关键字,推荐以下两种无侵入方案:
- 方案1:使用链接器
--wrap选项,完全不需要修改业务代码的函数定义和属性
操作步骤:- 移除foo_bar_run.h和foo_bar_run.c中所有
__attribute__((weak))声明,保持foo的原型为int foo()不变 - 编译main_run和原有逻辑完全一致,无需额外参数:
gcc another_main.c foo_bar_run.c -o main_run - 编译测试程序时,给链接器传递wrap参数指定要打桩的函数名,同时把测试代码里的桩函数命名为
__wrap_foo:
bar_test.c修改部分:
编译命令:int __wrap_foo() { printf("stubbed foo\n"); }gcc bar_test.c foo_bar_run.c -o main_test -Wl,--wrap=foo
该方案优势是如果桩函数需要调用原foo实现,直接调用__real_foo()即可,适合复杂打桩场景。
- 移除foo_bar_run.h和foo_bar_run.c中所有
- 方案2:利用链接器符号查找顺序,不需要修改任何代码的函数名和属性
链接器查找全局符号时会按输入文件的顺序取第一个匹配的定义,利用这个特性不需要加任何属性即可实现符号覆盖:
操作步骤:- 移除foo_bar_run.h和foo_bar_run.c中所有
__attribute__((weak))声明,保持foo的原型为int foo()不变 - 编译main_run和原有逻辑完全一致:
gcc another_main.c foo_bar_run.c -o main_run - 编译测试程序时,先把foo_bar_run编译为静态库,链接时优先传入bar_test的目标文件,再链接静态库:
# 编译测试代码目标文件 gcc -c bar_test.c -o bar_test.o # 编译业务代码目标文件并打包为静态库 gcc -c foo_bar_run.c -o foo_bar_run.o ar rcs libfoobar.a foo_bar_run.o # 链接时先放测试代码目标文件,再放静态库 gcc bar_test.o -o main_test -L. -lfoobar
- 移除foo_bar_run.h和foo_bar_run.c中所有
内容的提问来源于stack exchange,提问作者Guillaume D
相关产品推荐
相关产品推荐

