GCC 入门之 静态库 与 动态库
GCC 入门之 静态库 与 动态库
文件创建
在创建静态库和动态库之前,我们需要先创建我们需要的目标目录和文件
mkdir test1
touch hello.h
touch hello.c
touch main.c
hello.c 是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出 “Hello XXX!”。hello.h 为该函数库的头文件。main.c 为测试库文件的主程序,在主程序中调用了公用的函数 hello
程序如下:
hello.h:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
hello.c:
#include <stdio.h>
void hello(const char *name){
pritnf("Hello %s!\n",name);
}
main.c:
#include "hello.h"
int main(){
hello("ppqppl");
return 0;
}
编译
无论静态哭还是动态库都是由 .o 文件创建的,所以,要将 .c 文件编译成 .o 文件,直接使用 gcc 命令进行编译:
gcc -c hello.c hello.o
然后使用 ls 命令查看到编译出的文件,就说明编译成功:
由 .o 文件创建静态库
静态库文件夹的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为 .a 。例如:我们将创建的静态库命名为 myhello ,则静态库文件名就是 libmyhello.a 。在创建和使用静态库时,需要注意这点。创建静态库使用 ar 命令,在系统提示符下键入一下命令,将创建静态库文件 libmyhello.a :
ar -crv libmyhello.a hello.o
我们同样使用 ls 命令查看是否创建成功,如果成功会显示如下图:
使用静态库
可以直接通过以下代码使用静态库:
# 方法一
sudo gcc -o hello main.c -L. -lmyhello
# 方法二
sudo gcc main.c libmyhello.a -o hello
# 方法三
# 先生成 main.o
sudo gcc -c main.c -o main.o
# 再生成可执行文件
sudo gcc -o hello main.o libmyhello.a
测试是否成功
删除静态库,测试共用函数是否完全链接到目标文件 hello 中
sudo rm libmyhello.a
./hello
如果正常执行输出,就说明已经成功链接,成功链接测试结果如下图:
由 .o 文件创建动态库
动态库文件与静态库文件命名规范类似,只是后缀不同,动态库的后缀为 .so
用 gcc 命令创建动态库:
gcc -shared -fPIC -o libmyhello.so hello.o
我们同样使用 ls 命令查看是否创建成功,如果成功会显示如下图:
使用动态库
动态库与静态库使用方法完全一样,gcc 命令如下:
# 方法一
gcc -c hello main.c -L .lmyhello
# 方法二
gcc main.c libmyhello.so -o hello
# 方法三
# 先生成 main.o
sudo gcc -c main.c -o main.o
# 再生成可执行文件
sudo gcc -o hello main.o libmyhello.a
动态库与静态库有一些不同,使用静态库会从生成库的位置寻找静态库,而动态库,是从 /usr/lib
寻找,直接运行 hello 可执行文件会报错如下图:
所以,需要将动态库移动到上述目录下,才能正确运行,运行结果如下图:
测试是否成功
删除静态库,测试共用函数是否完全链接到目标文件 hello 中
sudo rm libmyhello.so
./hello
运行输出结果如下图:
说明在链接动态库之后,动态库源文件不能删除,否则程序将不能运行
静态库与动态库对比
这里主要介绍搜索路径的对比
在 Linux 系统中,GCC 编译链接时的动态库搜索路径的顺序通常为:首先从 GCC 命令参数 -L 制定的路径寻找; 再从环境变量 LIBRARY_PATH 指定的路径寻址; 再从默认路径 /lib、/usr/lib、/usr/local/lib 寻找
在 Linux 系统中,执行二进制文件时的动态哭搜索路径的顺序通常为:首先搜索编译目标代码时制定的搜索路径; 再从环境变量 LD_LIBRARY_PATH 指定的路径寻址; 再从配置文件 /etc/ld.so.conf 中指定的动态库搜索路径; 再从默认路径 /lib、/usr/lib 寻找
静态库与动态库共存?
从上述步骤我们可以成功链接静态库和动态库,我们也可以知道链接静态库之后,库文件可以删除,链接动态库之后,库文件不能删除,并且还要放到指定的路径之下/usr/lib
那么,如果我们同时链接静态库和动态库,会调用静态库还是动态库还是直接报错?
首先,先编译生成静态库和动态库,按照前面介绍的方法即可。
然后编译生成可执行文件,并运行:
如图可知,由于链接动态库和静态库的路径可能有重合,所以如果在路径中,我们在编译的时候同时链接静态库和动态库(同名时),会优先使用动态库,如果要链接静态库,则可以制定 gcc 选项 - static ,该选项会强制使用静态库进行链接
在 Linux 系统中,可以用 ldd 命令查看一个可执行程序依赖的共享库
链接器链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常被链接为不同的段,常见的段譬如:.text、.data、.rodata、.bss等
注意: 关于 ELF 文件的介绍,请看:GCC 一步到位的 ELF 部分
实例分析
通过 sub1.o add1.o 生成静态库:
链接静态库,并查看文件大小如下:
链接动态库,并查看文件大小如下:
由此可见,两次生成的可执行文件大小相同,但是动态库的大小明显要大于静态库大小
参数补充
补充 1:编译参数解析
最主要的是 GCC 命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:
gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gcc main.c libmyhello.a -o hello”)
-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。
LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到同样的目的,不过如果没有 root 权限,那么只能采用输出 LD_LIBRARY_PATH 的方法了。
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I”include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
补充 2:
从上述可知,如何找到生成的动态库有 3 种方式:
(1)把库拷贝到/usr/lib 和/lib 目录下。
(2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。例如动态库 libhello.so 在/home/example/lib 目录下:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/example/lib
(3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。这样,加入的目录下的所有库文件都可见。
附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:
g++ -o main main.cpp -L/usr/lib libpthread.a
必须这样 g++ -o main main.cpp -L/usr/lib -lpthread 才正确 。
自定义的库考到/usr/lib 下时,g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a 会出错,但是这样 g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass 就正确了