技术书籍精读笔记:程序员的自我修养-链接,转载与库(肆)

本文讲解了编译链接中的强符号与弱符号、强引用与弱引用的概念及其处理规则。强符号包括已初始化的全局变量和函数定义,弱符号包括未初始化的全局变量和显式声明为weak的属性。链接器遵循三条规则处理符号冲突:禁止多重强定义、优先选择强符号、弱符号选择最大空间类型。通过弱引用特性,可以实现库函数覆盖、功能模块的灵活裁剪等应用,如判断单线程/多线程模式。文中提供了具体代码示例说明这些特性的实际应用。

作者
WildPointer
发布
2025.08.10
专栏
技术书籍精读笔记
阅读
约 3 分钟 / 611 次原文浏览

前言

本小节将对目标文件中存在的强符号和弱符号,强引用和弱引用进行讲解,什么是所谓的强符号和弱符号?在链接器中会按什么规则来处理多次定义的符号?针对这些强引用和弱引用的特性,我们能实现什么功能?以上就是本篇博客的主要内容,对于内容有差异欢迎在评论区进行补充讲解,谢谢!!!


强符号和弱符号的定义

在目标文件中,普通的函数定义和已初始化的全局变量定义是强符号,而未初始化的全局变量(或显式声明为 __attribute__((weak)))是弱符号。可以参考以下代码进行直观的理解强符号和弱符号:

cpp
int x = 10;       // 已初始化的全局变量 → 强符号void foo() {}     // 函数定义 → 强符号int y;            // 未初始化的全局变量 → 弱符号void bar() __attribute__((weak)); // 弱函数

链接器处理强符号和弱符号的规则

对于不同的符号,链接器处理的规则也不一样,具体如下:
1.规则一:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器会报符号重复定义错误。

2.规则二:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。

3.规则三:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。例如目标文件A定义全局变量global为int类型,占用4个字节;目标文件B定义global为double类型,占用8个字节,那么目标文件A和B链接以后,符号global占8个字节。

以上是链接器对于强符号和弱符号的处理规则,这就是我们编程中可能会出现符号重复定义的原因。
PS:尽量不要使用多个不同类型的弱符号,否则容易导致很难发现的程序错误


强引用和弱引用的定义

在目标文件中,代码中对某个符号的普通调用或访问是强引用,通过(__attribute__((weakref)) 或 #pragma weak)显式声明一个引用为弱引用。具体可以参考以下示例:

cpp
extern void func();  // 强引用void foo() __attribute__((weak));    // 弱引用

针对强引用和弱引用的特性实现功能

对于强引用和弱引用的特性,在许多第三方库中通过定义弱符合,使其能被用户定义的强符号所覆盖,从而使程序可以使用自定义版本的库函数;或者程序可以对某些扩展功能模块的引用定义为弱引用,在将扩展模块与程序链接在一起时,功能模块就可以正常使用;如果去掉了某些功能模块,程序也可以正常链接,只是缺少了相应的功能,这使得程序的功能更加容易裁剪和组合。

例如在Linux的程序中,如果应该程序被涉及成可以支持单线程或多线程的模式,就可以通过弱引用的方法来判断当前的程序是链接到了单线程的Glibc库还是多线程的Globc库,从而执行单线程版本或者多线程订版本的程序。具体的代码如下:

cpp
#include <stdio.h>#include <pthread.h>int pthread_create(    pthread_t*,    const pthread_attr_t*,    void* (*) (void*),    void*) __attributr__ ((weak))); int main(){    if(pthread_create){        printf("这是多线程版本");    }    else{        printf("这是单线程版本");    }}
6次原文点赞;这里的喜欢仅保存在本机
WildPointer

专注系统编程、工程实践与底层技术,记录 C++、Qt、OpenCV 与 VTK 的学习和实践。