弱符号与强符号

弱符号与强符号 http://book.51cto.com 2009-04-22 12:52 俞甲子/石凡/潘爱民 人民邮电出版社 我要评论(0) 摘要:《程序员的自我修养:链接、装载与库》第3章目标文件里有什么。本章介绍COFF目标文件格式和源代码编译后如何在目标文件中存储。本节为大家介绍弱符号与强符号。 标签:弱符号 强符号 程序员 自我修养 程序员的自我修养:链接、装载与库 限时报名参加“甲骨文全球大会·2010·北京”及“JavaOne和甲骨文开发者大会2010” 3.5.5 弱符号与强符号 我们经常在编程中碰到一种情况叫符号重复定义。多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误。比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错: b.o:(.data+0x0): multiple definition of `global' a.o:(.data+0x0): first defined here 这种符号的定义可以被称为强符号(Strong Symbol)。有些符号的定义可以被称为弱符号(Weak Symbol)。对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。我们也可以通过GCC的"__attribute__((weak))"来定义任何一个强符号为弱符号。注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用。比如我们有下面这段程序: extern int ext; int weak; int strong = 1; __attribute__((weak)) weak2 = 2; int main() { return 0; } 上面这段程序中,"weak"和"weak2"是弱符号,"strong"和"main"是强符号,而"ext"既非强符号也非弱符号,因为它是一个外部变量的引用。针对强弱符号的概念,链接器就会按如下规则处理与选择被多次定义的全局符号: 规则1:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器报符号重复定义错误。 规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。比如目标文件A定义全局变量global为int型,占4个字节;目标文件B定义global为double型,占8个字节,那么目标文件A和B链接后,符号global占8个字节(尽量不要使用多个不同类型的弱符号,否则容易导致很难发现的程序错误)。 弱引用和强引用 目前我们所看到的对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,它们须要被正确决议,如果没有找到该符号的定义,链接器就会报符号未定义错误,这种被称为强引用(Strong Reference)。与之相对应还有一种弱引用(Weak Reference),在处理弱引用时,如果该符号有定义,则链接器将该符号的引用决议;如果该符号未被定义,则链接器对于该引用不报错。链接器处理强引用和弱引用的过程几乎一样,只是对于未定义的弱引用,链接器不认为它是一个错误。一般对于未定义的弱引用,链接器默认其为0,或者是一个特殊的值,以便于程序代码能够识别。弱引用和弱符号主要用于库的链接过程,我们将在"库"这一章再来详细讲述。弱符号跟链接器的COMMON块概念联系很紧密,我们在后面"深入静态链接"这一章中的"COMMON块"一节还会回顾弱符号的概念。 在GCC中,我们可以通过使用"__attribute__((weakref))"这个扩展关键字来声明对一个外部函数的引用为弱引用,比如下面这段代码: __attribute__ ((weakref)) void foo(); int main() { foo(); } 我们可以将它编译成一个可执行文件,GCC并不会报链接错误。但是当我们运行这个可执行文件时,会发生运行错误。因为当main函数试图调用foo函数时,foo函数的地址为0,于是发生了非法地址访问的错误。一个改进的例子是: __attribute__ ((weakref)) void foo(); int main() { if(foo) foo(); } 这种弱符号和弱引用对于库来说十分有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖,从而使得程序可以使用自定义版本的库函数;或者程序可以对某些扩展功能模块的引用定义为弱引用,当我们将扩展模块与程序链接在一起时,功能模块就可以正常使用;如果我们去掉了某些功能模块,那么程序也可以正常链接,只是缺少了相应的功能,这使得程序的功能更加容易裁剪和组合。 在Linux程序的设计中,如果一个程序被设计成可以支持单线程或多线程的模式,就可以通过弱引用的方法来判断当前的程序是链接到了单线程的Glibc库还是多线程的Glibc库(是否在编译时有-lpthread选项),从而执行单线程版本的程序或多线程版本的程序。我们可以在程序中定义一个pthread_create函数的弱引用,然后程序在运行时动态判断是否链接到pthread库从而决定执行多线程版本还是单线程版本: #include #include int pthread_create( pthread_t*, const pthread_attr_t*, void* (*)(void*), void*) __attribute__ ((weak)); int main() { if(pthread_create) { printf("This is multi-thread version!/n"); // run the multi-thread version // main_multi_thread() } else { printf("This is single-thread version!/n"); // run the single-thread version // main_single_thread() } } 编译运行结果如下: $ gcc pthread.c -o pt $ ./pt This is single-thread version! $ gcc pthread.c -lpthread -o pt $ ./pt This is multi-thread version!
### 符号符号的概念 在编程领域,特别是对于低级语言如C和C++来说,符号符号是链接阶段的重要概念。当提到符号时,指的是那些具有唯一性的全局实体——它们在一个程序内不允许重复定义;任何尝试重新定义这样的符号都会引发链接错误[^1]。 相比之下,符号则提供了一种更加灵活的方式去处理可能存在的重名情况。这类符号可以在不同的模块中有多个实例存在而不引起冲突,在最终决定哪个版本应该被使用的决策上给予链接器一定的自由度。具体而言: - 如果一个普通(即符号另一个相同名字但是标记为“”的符号共存,则优先选用前者; - 当仅有符号出现的时候,那么这些符号之间也会遵循某种规则来挑选出胜者,或者是简单地全部保留下来并让运行环境做进一步的选择[^2]。 这种机制不仅有助于构建更易于扩展的应用框架和支持动态加载组件的设计模式,而且还可以用来指定某些功能的默认行为作为最后手段。 #### 应用场景 为了更好地理解这两种类型的适用范围,下面列举了一些典型例子: - **默认实现**:通过声明一些接口方法为符号,开发者能够创建一套基础类库,其中包含了若干基本操作的具体形式。第三方使用者可以根据需求覆盖特定部分的功能,而无需担心破坏整个系统的稳定性。 - **插件架构**:设想有一个应用程序支持安装额外的小部件或服务包。利用符号特性可以让主程序预先准备好一组标准的服务入口点,只有当相应位置确实有可用更新时才会激活新的替代品。 - **条件编译/配置选项**:有时项目内部会依据平台差异或者其他因素调整一部分逻辑分支的表现形态。借助于符号可以帮助我们轻松管理多套方案之间的切换关系,减少冗余代码量的同时也提高了移植效率。 ### 编译器链接过程中的符号解析规则 在编译期间以及之后的静态链接环里,编译工具链负责收集来自各个目标文件(.o 或 .obj)里的所有导出项,并建立一张映射表用于后续查询匹配工作。针对每一对候选对象A 和 B (假设二者均指向同一标识符),其判定流程如下所示: 1. 若两者皆属于类别,则报错终止进程,因为这表明出现了命名空间污染现象。 2. 假设仅有一方是常规意义上的出口,则采纳之;另一端由于度较低自然被淘汰出局。 3. 对于两个都是属性的情况,默认情况下会选择任意一方加入到最终产物之中,不过实际做法可能会因具体的ABI(Application Binary Interface)规定有所不同。例如,在ELF(Executable and Linkable Format)体系下倾向于选取最先遇到的那个副本。 此外值得注意的一点在于,上述讨论主要围绕着静态连接展开论述。而在共享库(.so/.dll)环境下,事情变得更加复杂起来,涉及到延迟绑定、版本控制等多个方面的影响因子。然而无论如何变化,“优于”这条基本原则始终贯穿其间不变。 ```cpp // C++ Example of Weak Symbols Usage extern "C" { // Define a weak symbol function that can be overridden by other definitions. __attribute__((weak)) void myFunction() { printf("Default implementation\n"); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值