博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

链接库简介

Posted on 2020-09-25 22:04  面具下的戏命师  阅读(379)  评论(0编辑  收藏  举报

链接库

链接器把多个二进制的目标文件(object file)链接成一个单独的可执行文件。在链接过程中,它必须把符号(变量名、函数名等一些列标识符)用对应的数据的内存地址(变量地址、函数地址等)替代,以完成程序中多个模块的外部引用。链接器而言,链接库不过是一个具有许多目标文件的集合,它们在一个文件中以方便处理。

链接器也必须将程序中所用到的所有C标准库函数加入其中,标准库的大部分函数通常放在文件 libc.a 中,或者放在用于共享的动态链接文件 libc.so 中。当使用 GCC 编译和链接程序时,GCC 默认会链接 libc.a 或者 libc.so,但是对于其他的库(例如非标准库、第三方库等),就需要手动添加。

手动添加链接库

令人惊讶的是,标准头文件 <math.h> 对应的数学库默认也不会被链接,如果没有手动将它添加进来,就会发生函数未定义错误。GCC 的-l选项(小写的 L)可以让我们手动添加链接库。下面我们编写一个数学程序 main.c,并使用到了 cos() 函数,它位于 <math.h> 头文件:

#include <stdio.h>      /* printf */
#include <math.h>       /* cos */
#define PI 3.14159265
int main ()
{
    double param, result;
    param = 60.0;
    result = cos ( param * PI / 180.0 );
    printf ("The cosine of %f degrees is %f.\n", param, result );
    return 0;
}

如果我们不使用 -l 选项:GCC 编译器无法找到 cos() 这个函数。为了编译这个 main.c,必须使用-l选项,以链接math库:

数学库的文件名是 libm.a。前缀lib和后缀.a是标准的,m是基本名称,GCC 会在-l选项后紧跟着的基本名称的基础上自动添加这些前缀、后缀,本例中基本名称为 m。

链接其他目录的库

通常,GCC 会自动在标准库目录中搜索文件,例如 /usr/lib,如果想链接其它目录中的库,就得特别指明。有三种方式可以链接在 GCC 搜索路径以外的链接库。

1) 把链接库作为一般的目标文件,为 GCC 指定该链接库的完整路径与文件名。

例如,如果链接库名为 libm.a,并且位于 /usr/lib 目录,那么下面的命令会让 GCC 编译 main.c,然后将 libm.a 链接到 main.o:

gcc main.c -o main.out /usr/lib/libm.a

2) 使用-L选项,为 GCC 增加另一个搜索链接库的目录:

gcc main.c -o main.out -L/usr/lib -lm

可以使用多个-L选项,或者在一个-L选项内使用冒号分割的路径列表。

3) 把包括所需链接库的目录加到环境变量 LD_LIBRARY_PATH 中。

动态链接库和静态连接库

库文件用于程序的链接阶段,但编译器提供有 2 种实现链接的方式,分别称为静态链接方式和动态链接方式,其中采用静态链接方式实现链接操作的库文件,称为静态链接库;采用动态链接方式实现链接操作的库文件,称为动态链接库。

1、静态库文件

静态链接库实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,GCC 编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。
使用静态库文件实现程序的链接操作,既有优势也有劣势:

  • 优势是,生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强);
  • 劣势是,如果程序文件中多次调用库中的同一功能模块,则该模块代码势必就会被复制多次,生成的可执行文件中会包含多段完全相同的代码,造成代码的冗余。

使用动态链接库生成的可执行文件相比,静态链接库生成的可执行文件的体积更大。在 Linux 发行版系统中,静态链接库文件的后缀名通常用 .a 表示;在 Windows 系统中,静态链接库文件的后缀名为 .lib。

2、动态库文件

动态链接库,又称为共享链接库。和静态链接库不同,采用动态链接库实现链接操作时,程序文件中哪里需要库文件的功能模块,GCC 编译器不会直接将该功能模块的代码拷贝到文件中,而是将功能模块的位置信息记录到文件中,直接生成可执行文件。

显然,这样生成的可执行文件是无法独立运行的。采用动态链接库生成的可执行文件运行时,GCC 编译器会将对应的动态链接库一同加载在内存中,由于可执行文件中事先记录了所需功能模块的位置信息,所以在现有动态链接库的支持下,也可以成功运行。

采用动态链接库实现程序的连接操作,其优势和劣势恰好和静态链接库相反:

  • 优势是,由于可执行文件中记录的是功能模块的地址,真正的实现代码会在程序运行时被载入内存,这意味着,即便功能模块被调用多次,使用的都是同一份实现代码(这也是将动态链接库称为共享链接库的原因)。
  • 劣势是,此方式生成的可执行文件无法独立运行,必须借助相应的库文件(可移植性差)。

和使用静态链接库生成的可执行文件相比,动态链接库生成的可执行文件的体积更小,因为其内部不会被复制一堆冗余的代码。在 Linux 发行版系统中,动态链接库的后缀名通常用 .so 表示;在 Windows 系统中,动态链接库的后缀名为 .dll。

gcc一次处理多个文件

创建了 2 个 C 语言源程序文件,分别为 demo1.c 和 demo2.c。一条 gcc(g++)指令往往可以一次性处理多个文件:

可以看到,demo1.c 和 demo2.c 的编译过程可以共用一条 gcc -c 指令,其默认情况下会分别生成 demo1.o 和 demo2.o 目标文件。需要注意的是,此方法无法使用 -o 选项分别将编译 demo1.c 和 demo2.c 的目标代码输出到指定文件,也就是说如下这行代码是错误的:

 不仅如此,以下这些操作都可以共用一条 gcc 指令:

  • 将多个 C(C++)源文件加工为汇编文件或者目标文件;
  • 将多个 C(C++)源文件或者预处理文件加工为汇编文件或者目标文件;
  • 将多个 C(C++)源文件、预处理文件或者汇编文件加工为目标文件;
  • 同一项目中,不同的源文件、预处理文件、汇编文件以及目标文件,可以使用一条 gcc 指令,最终生成一个可执行文件。

GCC编译多文件项目

在一个 C(或者 C++)项目中,往往在存储多个源文件,如果仍按照之前“先单独编译各个源文件,再将它们链接起来”的方法编译该项目,需要编写大量的编译指令,事倍功半。事实上,利用 gcc 指令可以同时处理多个文件的特性,可以大大提高我们的工作效率。

举个例子,如下是一个拥有 2 个源文件的 C 语言项目:

[root@bogon demo]# ls
main.c  myfun.c
[root@bogon demo]# cat main.c
#include <stdio.h>
int main(){
    display();
    return 0;
}
[root@bogon demo]# cat myfun.c
#include <stdio.h>
void display(){
    printf("GCC:http://c.biancheng.net/gcc/");
}
[root@bogon demo]#

可以看到,该项目中仅包含 2 个源文件,其中 myfun.c 文件用于存储一些功能函数,以方便直接在 main.c 文件中调用。对于此项目,我们可以这样编译:

 cc 指令还可以直接编译并链接它们: