<!--title:Linux下静态库和动态库简介及示例-->
<!--category:技术学习-->
<!--tags:linux, c语言-->
<!--author:Neal-->
<!--date:2016-04-16-->

在实际的软件开发项目中，不是每一行代码都需要我们亲自写。在我们的软件产品中，有一些代码（尤其是函数）的出现频率很高，它们可以被当作公共代码来反复使用。为了避免重复劳动，我们就把这些公共代码编译为库文件，供需要的程序调用。在`Linux`中，库分为静态库和动态库两种。

### 简介
众所周知，程序一般需要经过预处理、编译、汇编和链接这几个步骤才能变成可执行的程序。在实际的软件开发中，对于一些需要被许多模块反复使用的公共代码，我们就将它们编译为库文件。

库是一种可执行代码的二进制形式，可以被操作系统载入内存执行。`Linux`支持的库分为静态库和动态库，动态库又称共享库。一般说来，`Linux`中的一些重要的库是存放在`lib`目录下的。

> 静态库文件的后缀为`.a`，在`Linux`下一般命名为`libxxx.a`。在链接步骤中，连接器将从静态库文件中取得所需的代码，复制到生成的可执行文件中。因此，整个库中的所有函数都被编译进了目标代码中。

> 动态库文件的后缀为`.so`，在`Linux`下一般命名为`libxxx.so`。相对于静态库，动态库在编译的时候并没有被编译进目标代码中，而是程序执行到相关函数时才调用库中对应的函数。

可以看到，静态库的优点是编译后的执行程序不需要外部的函数库支持，缺点是如果静态函数库改变了，那么你的程序必须重新编译；而动态库在多个应用程序都要使用同一函数库的时候就非常适合，但前提是程序的运行环境中必须提供相应的库。
不管是静态库，还是动态库，都是由`*.o`目标文件生成的。

### 静态库

    ~/test> ll
    -rw------- 1 neal neal 96 Nov  4 16:11 main.c
    -rw------- 1 neal neal 70 Nov  4 16:04 test.h
    -rw------- 1 neal neal 84 Nov  4 16:04 test_1.c
    -rw------- 1 neal neal 84 Nov  4 16:04 test_2.c
    -rw------- 1 neal neal 84 Nov  4 16:04 test_3.c
    ~/test> gcc -c test_1.c test_2.c test_3.c
    ~/test> ll
    -rw------- 1 neal neal   96 Nov  4 16:11 main.c
    -rw------- 1 neal neal   70 Nov  4 16:04 test.h
    -rw------- 1 neal neal   84 Nov  4 16:04 test_1.c
    -rw-rw-rw- 1 neal neal 1624 Nov  4 16:15 test_1.o
    -rw------- 1 neal neal   84 Nov  4 16:04 test_2.c
    -rw-rw-rw- 1 neal neal 1624 Nov  4 16:15 test_2.o
    -rw------- 1 neal neal   84 Nov  4 16:04 test_3.c
    -rw-rw-rw- 1 neal neal 1624 Nov  4 16:15 test_3.o
    ~/test> ar -r libtest.a test_1.o test_2.o test_3.o
    ar: creating libtest.a
    ~/test> ll
    -rw------- 1 neal neal   96 Nov  4 16:11 main.c
    -rw-rw-rw- 1 neal neal 5158 Nov  4 16:15 libtest.a
    -rw------- 1 neal neal   70 Nov  4 16:04 test.h
    -rw------- 1 neal neal   84 Nov  4 16:04 test_1.c
    -rw-rw-rw- 1 neal neal 1624 Nov  4 16:15 test_1.o
    -rw------- 1 neal neal   84 Nov  4 16:04 test_2.c
    -rw-rw-rw- 1 neal neal 1624 Nov  4 16:15 test_2.o
    -rw------- 1 neal neal   84 Nov  4 16:04 test_3.c
    -rw-rw-rw- 1 neal neal 1624 Nov  4 16:15 test_3.o
    ~/test> gcc -o test main.c libtest.a
    ~/test> ll
    -rw------- 1 neal neal    96 Nov  4 16:11 main.c
    -rwxrwxrwx 1 neal neal 12008 Nov  4 16:16 test
    -rw-rw-rw- 1 neal neal  5158 Nov  4 16:15 libtest.a
    -rw------- 1 neal neal    70 Nov  4 16:04 test.h
    -rw------- 1 neal neal    84 Nov  4 16:04 test_1.c
    -rw-rw-rw- 1 neal neal  1624 Nov  4 16:15 test_1.o
    -rw------- 1 neal neal    84 Nov  4 16:04 test_2.c
    -rw-rw-rw- 1 neal neal  1624 Nov  4 16:15 test_2.o
    -rw------- 1 neal neal    84 Nov  4 16:04 test_3.c
    -rw-rw-rw- 1 neal neal  1624 Nov  4 16:15 test_3.o
    ~/test> ./test
    this is in test_1......
    this is in test_2......
    this is in test_3......

我们可以看到，生成静态库文件的命令是`ar -r libtest.a test_1.o test_2.o test_3.o`，而将静态库文件编译进代码的命令是`gcc -o test main.c libtest.a`。

这样生成了静态库文件`libtest.a`之后，如果还有其他程序要调用`test_1.c`、`test_2.c`、`test_3.c`中实现的函数，只需要将`test.h`和`libtest.a`拷贝到对应的代码工程中，然后执行类似`gcc -o test main.c libtest.a`这样的命令即可。

### 动态库

    ~/test> ll
    -rw------- 1 neal neal   70 Nov  5 13:44 so_test.h
    -rw------- 1 neal neal  105 Nov  4 15:25 test.c
    -rw------- 1 neal neal   84 Nov  4 15:25 test_a.c
    -rw------- 1 neal neal   84 Nov  4 15:25 test_b.c
    -rw------- 1 neal neal   84 Nov  4 15:25 test_c.c
    ~/test> gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
    ~/test> gcc test.c -L. -ltest -o test
    ~/test> ll
    -rwxrwxrwx 1 neal neal  8309 Nov  5 13:46 libtest.so
    -rw------- 1 neal neal    70 Nov  5 13:44 so_test.h
    -rwxrwxrwx 1 neal neal 11883 Nov  5 13:46 test
    -rw------- 1 neal neal   105 Nov  4 15:25 test.c
    -rw------- 1 neal neal    84 Nov  4 15:25 test_a.c
    -rw------- 1 neal neal    84 Nov  4 15:25 test_b.c
    -rw------- 1 neal neal    84 Nov  4 15:25 test_c.c
    ~/test> ./test
    this is in test_a...
    this is in test_b...
    this is in test_c...

注意，`./test`命令执行成功的前提是在环境变量中添加了`.so`文件所在的路径，这个路径可以在`.bash_profile`文件的`LD_LIBRARY_PATH`变量的值中添加。

我们可以看到，多个文件生成动态库文件的命令是`gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so`，而将动态库文件编译进代码的命令是`gcc test.c -L. -ltest -o test`（`-L.`表示当前路径）。

这样生成了动态库文件`libtest.so`之后，如果还有其他程序要调用`test_a.c`、`test_b.c`、`test_c.c`中实现的函数，只需要将`so_test.h`和`libtest.so`拷贝到对应的代码工程中，然后执行类似`gcc test.c -L. -ltest -o test`这样的命令即可（前提是`libtest.so`所在的路径在环境变量中设置正确）。

### 总结
有关生成静态库和动态库的命令，说明如下：

第一，在本文中，我们使用的生成静态库的命令形如`ar -r test.a test.o`，其中，`-r`是`replace`的意思，表示如果当前插入的模块名已经在库中存在，则替换同名的模块。我们也可以用形如`ar -cr test.a test.o`的命令来生成静态库，其中`-c`是`create`的意思，表示生成。

第二，在本文中，我们使用的生成动态库文件的命令形如`gcc test_a.c -fPIC -shared -o libtest.so`，其中，`fPIC`表示编译为位置独立的代码，`shared`表示生成的库为共享库。将动态库文件编译进代码的命令是`gcc test.c -L. -ltest -Wl,-rpath=. -o test`，`-L`指定库查找的位置（注意`L`后面还有`'.'`），表示在当前目录下查找（如果在当前目录下的`lib`目录下查找，可以写成`-L./lib`）；`-l`则指定函数库名，其中的`lib`和`.so`省略（如这里的`libtest.so`就简写为`test`）；`-Wl,-rpath=.`则指定程序运行时优先查找的库路径，这里表示优先查找当前目录是否有`libtest.so`（如果加上这个选项，你可以不必设置`LD_LIBRARY_PATH`这个环境变量）。

第三，使用`ldd`命令可以查看一个可执行程序依赖的共享库，该命令的使用示例如下所示：

    ~/test> ldd test
            linux-vdso.so.1 =>  (0x00007fff1db6e000)
            libtest.so => ./libtest.so (0x00007fdbfff21000)
            libc.so.6 => /lib64/libc.so.6 (0x00007fdbffb95000)
            /lib64/ld-linux-x86-64.so.2 (0x00007fdc00124000)

可以看到，可执行文件`test`依赖于四个共享库，其中`libtest.so`位于当前目录下。

第四，虽然普通编程用不到，但想反汇编的话绝对少不了目标文件格式分析工具：

* `ar`命令可以用来创建，修改和提取静态库；
* `nm`命令可以列出`object`文件的符号表；
* `objdump`命令用来查看和反汇编`object`文件，程序破解必备工具；
* `objcopy`命令用来拷贝出`object`文件的特定部分，甚至将`object`文件转换为`binary`格式；
* `readelf`命令可以查看`ELF`格式文件的符号表。
