版权归原作者所有,如有侵权,请联系我们

[科普中国]-弱符号

科学百科
原创
科学百科为用户提供权威科普内容,打造知识科普阵地
收藏

弱符号表示在链接可执行文件和可链接格式(ELF)对象文件期间特别注释的符号。默认情况下,没有任何注释,目标文件中的符号很强。 在链接期间,强符号可以覆盖同名的弱符号。相反,共享名称的两个强符号在链接时产生链接错误。 链接二进制可执行文件时,弱声明的符号不需要定义。相比之下,(默认情况下)没有定义的声明的强符号会触发未定义的符号链接错误。

C或C ++语言标准没有提到弱符号;因此,将它们插入代码并不是非常便携。 即使两个平台支持用于将符号标记为弱的相同或类似语法,语义也可能在细微点上不同,例如, 动态链接期间弱符号是否会失去语义。

语法GNU编译器集合和Solaris Studio C编译器共享相同的语法,用于将符号注释为弱,即特殊的#pragma,#pragma weak,以及函数和变量属性__attribute __((weak))。

编译// function declaration#pragma weak power2 int power2(int x);属性// function declaration int __attribute__((weak)) power2(int x); // or int power2(int x) __attribute__((weak)); // variable declaration;extern int __attribute__((weak)) global_var;支持平台nm命令标识目标文件,库和可执行文件中的弱符号。 在Linux上,如果弱默认定义可用,则弱函数符号标记为“W”,如果不可用,则标记为“w”。 弱定义的变量符号标有“V”和“v”。 在Solaris上,“nm”为弱符号打印“WEAK”而不是“GLOB”。1

实例静态实例main.c:

#include #include #include "power_slow.h" int main(int argc, char **argv){ fprintf(stderr, "power3() = %d\n", power3(atoi(argv[1]))); return 0;}power_slow.h:

#ifndef POWER2_SLOW_H#define POWER2_SLOW_H // alternative syntax// #pragma weak power2int __attribute__((weak)) power2(int x) // alternatively after symbol // __attribute__((weak)) ; int power3(int x); #endifpower_slow.c:

#include #include "power_slow.h" int power2(int x){ fprintf(stderr, "slow power2()\n"); return x*x;} int power3(int x){ return power2(x)*x;}power.c:

#include int power2(int x){ fprintf(stderr, "fast power2()\n"); return x*x;}Build commands:

cc -g -c -o main.o main.ccc -g -c -o power_slow.o power_slow.ccc -g -c -o power.o power.ccc main.o power_slow.o -o slowcc main.o power_slow.o power.o -o fast输出

$ ./slow 3slow power2power3() = 27$ ./fast 3fast power2power3() = 27删除weak属性并重新执行build命令时,最后一个失败并显示以下错误消息(在Linux上):

multiple definition of `power2'倒数第二个仍然成功,而./slow具有相同的输出。

共享实例从前面的例子中取main.c并添加:

#ifndef NO_USER_HOOKvoid user_hook(void){ fprintf(stderr, "main: user_hook()\n");}#endif用以下内容替换power_slow.c:

#include #include "power_slow.h" void __attribute__((weak)) user_hook(void);#ifdef ENABLE_DEFvoid user_hook(void){ fprintf(stderr, "power_slow: user_hook()\n");}#endif int power2(int x){ if (user_hook) // only needed ifndef ENABLE_DEF user_hook(); return x*x;} int power3(int x){ return power2(x)*x;}构建命令:

cc -g -c -o main.o main.ccc -g -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -Lpwd -Wl,-Rpwd -lpowerslow -o main cc -g -DENABLE_DEF -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -Lpwd -Wl,-Rpwd -lpowerslow -o main2 cc -g -DNO_USER_HOOK -c -o main.o main.ccc -g -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -Lpwd -Wl,-Rpwd -lpowerslow -o main3 cc -g -DNO_USER_HOOK -c -o main.o main.ccc -g -DENABLE_DEF -fpic -c -o power_slow.po power_slow.ccc -shared -fpic -o libpowerslow.so power_slow.pocc main.o power_slow.po -Lpwd -Wl,-Rpwd -lpowerslow -o main4 输出

$ ./main 3main: user_hook()power3() = 27$ ./main2 3main: user_hook()power3() = 27$ ./main3 3power3() = 27$ ./main4 3power_slow: user_hook()power3() = 27删除weak属性并重新执行构建命令不会产生构建错误,并导致main和main2的输出(在Linux上)相同。 main3的构建命令导致以下警告和错误消息(在Linux上):

warning: the address of ‘user_hook’ will always evaluate as ‘true’libpowerslow.so: undefined reference to `user_hook'警告由编译器发出,因为它可以静态地确定if(user_hook)表达式user_hook总是评估为true,因为它包含ELF跳转表条目。 错误消息由链接器发出。 main4的构建包含相同的警告但没有链接错误。

应用弱符号可以用作提供功能的默认实现的机制,其可以在链接时由更专业(例如,优化的)替换。 然后将默认实现声明为弱,并且在某些目标上,具有强声明符号的目标文件将添加到链接器命令行。

如果库将符号定义为弱符号,则链接该库的程序可以自由地提供强大的符号,例如,用于自定义目的。

弱符号的另一个用例是维护二进制向后兼容性。

相关方法C预处理器(CPP)条件结构也可用于在符号的不同版本之间切换。 与弱符号的区别在于弱符号由链接器解释。 CPP在C编译器之前编译每个转换单元期间运行。

构建过程(例如make)可以以条件方式实现,使得仅创建符号的不同版本或者根据目标使用和链接不同(专用)库。

本词条内容贡献者为:

王慧维 - 副研究员 - 西南大学