假设要通过printf函数打印“hello,world”,程序开始运行后,经过预处理、编译、汇编等过程,在链接阶段需要调用printf函数,那么就会去C语言库里找printf函数
Windows环境下,链接找的是 .lib 文件,运行找的是 .dll 文件;Linux环境下,链接和运行默认找的都是 .so 文件(可以调整)
目录
一、动态库和静态库的区别
库是一个二进制文件,包含的代码可以被程序调用,如标准库、线程库。Windows 和 Linux下的库文件格式不兼容。
- Windows环境:静态库是 .lib 文件,共享库是 .dll 文件
- Linux环境:静态库是 .a 文件,共享库是 .so 文件(一般放在 /lib 或者 /usr/lib 目录下)
1、静态库(静态链接)
之所以称为静态库,是因为在链接阶段,把要用到的第三方库的代码加入到目标可执行文件中,这样的话,该可执行文件在运行时就无需去找库文件了。
可执行文件链接静态库的方式,称为静态链接。正是因为如此,这个时候的可执行文件会比较大。
静态库命令规则:以 lib 作为前缀,以 .a作为后缀。如 libxxx.a
优点:程序中已经包含了代码,运行时不需要再加载静态库,运行速度更快
缺点:占用更多的磁盘空间,静态库升级以后,程序需要重新编译链接。
2、动态库(动态链接)
在编译链接时不会把要用到的库文件代码加入到可执行文件中,而是在链接阶段需要用到的时候,去找到对应的库,然后再调用库里的函数。和静态库不同,动态库中保存的是偏移位置,在要用的时候,根据偏移量找到对应的文件。
可执行文件链接动态库的方式,称为动态链接。避免了空间浪费,需要更新库的时候,无需更新执行文件。
动态库:以 lib 作为前缀,以 .so作为后缀。如 libxxx.so
特点:
- 程序不会包含库中代码,尺寸小
- 多个程序可共享一个库
- 库升级方便。无需重新编译程序
二、创建静态库
一个完整的库包含 库文件本身、头文件、说明文档,现在我们仅需要关注前两者。一般制作动静态库只需要让程序进行到汇编阶段即可,即生成 .o 文件就可以停下来了。因为到了链接阶段,编译器就会去执行 main 函数的内容,制作库的时候没有这个东西。
制作动静态库的本质:就是将所有的.o文件打包。——》使用ar或者gcc打包
1、生成 .o 文件
现在我们已经在一个目录下创建好了生成 .o 文件所需的依赖
我们在命令行输入 gcc -o hello.o -c hello.c
2、创建静态库(ar命令)
(1) ar 命令
一般通过 ar 命令来创建静态库, ar命令的选项解析如下:
选项 | 含义 |
-c | 禁止在创建库时产生正常消息 |
-r | 如果指定的文件已经存在于库中,则替换它 |
-s | 无论 ar 命令是否修改了库文件内容,都强制重新生成库符号表 |
-v | 将建立新库的逐个文件的描述输出到标准输出(将生成新库过程中每个文件做了啥打印到终端) |
-q | 将指定文件添加到库文件的末尾 |
-t | 将库的目录写至标准输出 |
(2) 使用 ar 命令生成静态库
创建静态库时,使用上面的 -rsv 三个选项即可,创建静态库的命令如下:
# 静态库必须以 lib 为前缀,文件拓展名为 .a
ar -rsv libhello.a hello.o
3、使用静态库
假设我们在myTest.c中使用的了上述静态库中的函数,那么在使用 gcc 命令生成执行文件的时候就需要告诉编译器静态库在哪个目录下,静态库叫什么
在命令行输入如下命令:
# -L 表示添加库的搜索路径,. 代表当前目录
# -l 注意是小写的L,代表库的名称
# 静态库的真正名称 = libhello.a 去掉前缀 lib 和 后缀 .a
gcc -o myTest myTest.c -L. -lhello
三、创建动态库
编译器在链接动态库的时候,是去指定目录下寻找的,可执行文件因为不包含库中代码,所以在编译的时候即便是指定了搜索目录也没有意义。
1、创建 .o 文件
我们移除之前的静态库,依然使用之前的 hello.c 来生成目标文件
我们在命令行输入 gcc -fPIC -o hello.o -c hello.c 。如果 hello.c 用到了其他 .c文件,比如add.c、sub.c,相关文件也需要生成目标文件。
gcc -fPIC -o add.o -c add.c
gcc -fPIC -o sub.o -c sub.c
gcc -fPIC -o hello.o -c hello.c
注意:这里新增了 -fPIC 选项,实际上改变的是 hello.o 的符号表,我们可以使用 nm 命令查看hello.o的符号表,多出了一个全局偏移表,没有这个偏移表是无法动态编译的。
2、创建动态库( gcc -shared )
创建动态库使用的命令是
# gcc -shared -o libxxx.so xxx.o xxx.o ...
gcc -shared -o libhello.so hello.o add.o sub.o
3、使用动态库
(1)使用动态库可能存在的问题
在链接阶段就会去找对应的动态库,但是此时即便我们添加了搜索路径也依然找不到动态库。
这是因为编译器只会去库目录和环境变量中找动态库,Linux环境下的库文件一般都放在 /lib 或者 /usr/lib 目录下,所以如果我们要让程序能找到动态库,可以使用接下来说的两种方法。
(2) 让程序找到共享库的方法
方法一:将生成的动态库拷贝到 /usr/lib 或者 /lib 目录下(不推荐)
这种方法不大推荐,因为会污染系统库源
linux@linux:~/Templates/staticLib$ sudo cp libhello.so /usr/lib/
linux@linux:~/Templates/staticLib$ ./myTest
hello. world!
方法二:修改环境变量 LD_LIBRARY_PATH
环境变量 LD_LIBRARY_PATH是动态库的搜索路径,一般情况下为空,可执行文件运行时,会去这个环境变量中搜索动态库路径
linux@linux:~/Templates/staticLib$ export LD_LIBRARY_PATH=./
linux@linux:~/Templates/staticLib$ ./myTest
hello. world!
注意:第二种方法仅仅只是在当前 Shell 环境下有效,如果新开一个终端,相当于创建了一个新的Shell环境,该环境下的环境变量LD_LIBRARY_PATH是空的。
4、查看执行文件依赖的动态库
ldd 命令可以查看当前执行文件所链接的动态库
拓展:
还可以加 -r 选项查看更加详细的连接信息,有的时候可以通过这种方法发现库的隐藏问题
如果发现了问题,可以输入 c++filt 符号名 来确定到底是哪个文件出现了问题,这里的符号名指的是_ZN10vacTestAcc16vacTestSampEnAccEPdiPKc