我们把公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才和可执行程序一起被载入内存,因此在可执行程序运行时还需要动态库同时存在。

本文主要通过举例来说明在Linux中如何创建静态库和动态库,以及使用它们。

简单实例,两个模块:add和sub。

add.c

int add(int a, int b)
{
    return a+b;
}

sub.c

int sub(int a, int b)
{
    return a-b;
}

main.cpp

#include <stdio.h>
//调用函数,在发现函数未定义的时候,就会自动的去库中寻找函数的定义

int main(void)
{
    printf("1 + 2 =%d\n", add(1, 2));
    printf("1 - 2 =%d\n", sub(1, 2));
    return 0;
}

静态库和动态库的区别在于装入内存的策略不同,静态库在编译的时候会被编译到可执行程序里面,有几个引用库的程序内存中就有几份静态库的拷贝,而动态库则是在运行的时候装入内存,程序中只有一个动态库的入口,在调用到动态库的地方去内存中找函数。

1.编译静态库

1)编译目标文件

无论是静态库还是动态库,都是由.o文件创建的(动态库可以直接通过.c创建)。因此,我们必须将源程序.c通过gcc先编译成.o文件。利用以下命令得到.o文件。

# gcc -c add.c

# gcc -c sub.c

2)由.o文件创建静态库

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为static,则静态库文件名就是libstatic.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。

通过以下命令将创建静态库文件libstatic.a。

# ar cr libstatic.a add.o sub.o

3)在程序中使用静态库

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数链接到可执行程序中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。

# gcc main.c -L. -lstatic -o test

或者

# gcc main.c libstatic.a -o test

运行可执行程序

# ./test

ios 静态库 动态库 调用顺序 动态库和静态库的使用_静态库

删除静态库文件试试函数是否编译到可执行程序中了。 

ios 静态库 动态库 调用顺序 动态库和静态库的使用_静态库_02

程序正常运行,说明静态库中的公用函数已经连接到目标文件中了。 

编译脚本

static.sh

#!/bin/bash

if [ $1 == "clean" ]; then
    echo "clean"
    rm test sub.o add.o libstatic.a 
else
    echo "make static"

    if [ ! -f "add.o" ]; then
        gcc -c add.c #生成模块目标文件
    fi

    if [ ! -f "sub.o" ]; then
        gcc -c sub.c #生成模块目标文件
    fi

    if [ ! -f "libstatic.a" ]; then
            #将所有的目标文件链接成静态库
	    ar cr libstatic.a add.o sub.o
    fi

    if [ ! -f "main" ]; then
	    #使用库生成可执行文件
            #gcc main.c libstatic.a -o test 
	    gcc main.c -L. -lstatic -o test
    fi
fi

2.编译动态库库

继续创建动态库,依然是从生成.o文件开始。

1)生成.o文件

2)由.o文件创建动态库文件

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为dynamic,则动态库文件名就是 libdynamic.so。用 gcc 来创建动态库。

使用以下命令得到动态库文件 libdynamic.so。

# gcc -shared -o libdynamic.so add.o sub.o

也可以由.c 文件直接创建动态库:

# gcc -shared  -o libdynamic.so add.c sub.c

3)在程序中使用动态库

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我们先运行 gcc 命令生成目标文件,再运行它看看结果。

# gcc test.c -L. -ldynamic -o test

# ./test

ios 静态库 动态库 调用顺序 动态库和静态库的使用_目标文件_03

删除放置在/usr/lib目录下的动态链接库,则运行程序错误。

编译脚本

dynamic.sh

#!/bin/bash

if [ "$1" == "clean" ]; 
then
    echo "clean"
    sudo rm test sub.o add.o libdynamic.so
    sudo rm /usr/lib/libdynamic.so
else
    echo "make dynamic"

    if [ ! -f "add.o" ]; then
        gcc -c add.c  #生成模块目标文件
    fi

    if [ ! -f "sub.o" ]; then
        gcc -c sub.c  #生成模块目标文件
    fi

    if [ ! -f "libdynamic.so" ]; then
        #将所有的目标文件链接成动态库
        gcc -shared -o libdynamic.so add.o sub.o 
    fi

    if [ -f "/usr/lib/libdynamic.so" ]; then
        sudo rm /usr/lib/libdynamic.so
    fi
    
    #拷贝动态链接库
    cp libdynamic.so /usr/lib

    if [ ! -f "test" ]; then
        #使用库生成可执行文件
        gcc main.c -ldynamic -o test 
    fi
fi

3. 链接库的选择

使用静态库和使用动态库编译成可执行程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?

先删除其他文件,只留源文件,
在来创建静态库文件 libsame.a 和动态库文件 libsame.so。

# ar cr libsame.a add.o sub.o

# gcc -shared  add.o sub.o -o libsame.so

# ls

ios 静态库 动态库 调用顺序 动态库和静态库的使用_ios 静态库 动态库 调用顺序_04

# gcc -o test main.c -L. -lsame

# ./test

ios 静态库 动态库 调用顺序 动态库和静态库的使用_静态库_05


从程序运行的结果可知,当静态库和动态库同名时,gcc命令将优先使用动态库进行编译!

编译参数
-shared 该选项指定生成动态链接库,不用该标志则相当于生成一个可执行文件。
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L:表示要链接的库所在位置。
-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面加上.so 来确定库的名称。
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 文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。