Linux C/C++学习2:静态库与动态库


1. 函数库分类

  • 静态库:在编译过程中,将库函数代码直接加入到生成的可执行程序中,程序运行过程中不需要利用库函数;静态库命名规则一般是前缀lib+库名+.a,例如libm.a、libstdc++.a等;
  • 共享库:在编译过程中,只是在生成的可执行程序中简单指定需要使用的库函数信息,程序运行过程中需要利用库函数;共享库命名规则一般是前缀lib+库名+.so+版本号 ,例如libm.so.6、libc.so.6等;
  • 动态库:共享库的一种变化形式,目前大都采用共享库的方式。

2. 静态库的使用

静态库,也称作归档文件(archive),是一组目标文件(*.o)的集合。

  • 优点:整个函数库的所有数据都会被整合进目标代码中,编译后的执行程序不需要外部的函数库支持;
  • 缺点:利用静态函数库编译成的文件比较大,如果静态函数库改变了,那么程序必须重新编译;当同时运行程序并且它们都使用同一静态函数库时,内存中就会有同一函数的多份副本,而且在程序自身中也有多份同样的副本,这将消耗大量宝贵的内存和磁盘空间。

接下来,从具体的实例入手阐述静态库的相关操作,实例代码参见附录A。请按照附录A.1创建源码结构,按照A.2完成源码实现。

  • 说明:笔者想温习一下gcc相关选项参数,所以创建了模块化的源码结构。读者可以使用附录A.1源码结构,也可以将所有源文件、中间文件、结果文件都放到同一目录下。在大型项目中,建议使用模块化的结构。

2.1 生成目标文件

进入src/selfmath路径,执行下列指令,在build/obj路径下会生成相应的目标文件。

  • gcc -Wall -c add.c -o …/…/build/obj/add.o -I…/…/inc
  • gcc -Wall -c sub.c -o …/…/build/obj/sub.o -I…/…/inc
  • gcc -Wall -c mul.c -o …/…/build/obj/mul.o -I…/…/inc
  • gcc -Wall -c square_diff.c -o …/…/build/obj/square_diff.o -I…/…/inc
root@ubuntu:/opt/math# cd src/selfmath/
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c add.c -o ../../build/obj/add.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c sub.c -o ../../build/obj/sub.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c mul.c -o ../../build/obj/mul.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c square_diff.c -o ../../build/obj/square_diff.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# 
root@ubuntu:/opt/math/src/selfmath# tree ../../
../../
├── build
│   ├── lib
│   └── obj
│       ├── add.o					// 由add.c和selfmath.h生成的目标文件
│       ├── mul.o					// 由mul.c和selfmath.h生成的目标文件
│       ├── square_diff.o			// 由square_diff.c和selfmath.h生成的目标文件
│       └── sub.o					// 由sub.c和selfmath.h生成的目标文件
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath					// 执行命令路径
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 10 files
root@ubuntu:/opt/math/src/selfmath#

2.2 创建静态库

进入build/obj路径,执行下列指令,在build/lib路径下会生成相应的静态库。

  • ar cr …/lib/libselfmath.a add.o mul.o sub.o square_diff.o

ar函数说明:

  1. 用途:创建和修改函数库,或从函数库提取目标文件,下文中会进行相关操作。
  2. 举例:
    ar -rs lib-name list-of-files (将列表中的目标文件加入到库中,并产生索引文件)
    ar -ds lib-name list-of-files(将列表中的目标文件从库中删除,并产生索引文件)
    ar -x lib-name list-of-files(从库中提取列表中的目标文件,不修改库文件)
root@ubuntu:/opt/math/src/selfmath# cd ../../build/obj/
root@ubuntu:/opt/math/build/obj# ar cr ../lib/libselfmath.a add.o mul.o sub.o square_diff.o 
root@ubuntu:/opt/math/build/obj# 
root@ubuntu:/opt/math/build/obj# tree ../../
../../
├── build
│   ├── lib
│   │   └── libselfmath.a			// 由add.o、mul.o、sub.o和square_diff.o生成的静态库
│   └── obj							// 执行命令路径
│       ├── add.o
│       ├── mul.o
│       ├── square_diff.o
│       └── sub.o
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 11 files
root@ubuntu:/opt/math/build/obj# 

2.3 使用静态库

进入src/main路径,执行下列指令,在build路径下会生成最终的可执行文件。

  • gcc -Wall main.c -o …/…/build/main -I…/…/inc -L…/…/build/lib -lselfmath

一个容易忽略的顺序问题:静态库不能先于原程序链接,这是因为开始时还没有任何未定义的符号,库中的内容不会被链入,所以应该注意将静态库(-l选项)都写在最后。至于linux下静态库的依赖顺序问题,后续将另起博文研究。

root@ubuntu:/opt/math/build/obj# cd ../../src/main/
root@ubuntu:/opt/math/src/main# gcc -Wall main.c -o ../../build/main -I../../inc -L../../build/lib -lselfmath
root@ubuntu:/opt/math/src/main# 
root@ubuntu:/opt/math/src/main# tree ../../
../../
├── build
│   ├── lib
│   │   └── libselfmath.a
│   ├── main						// 由main.c、selfmath.h、libselfmath.a生成的可执行文件
│   └── obj
│       ├── add.o
│       ├── mul.o
│       ├── square_diff.o
│       └── sub.o
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 12 files
root@ubuntu:/opt/math/src/main# 
  • 执行 ./main,结果如下
root@ubuntu:/opt/math/src/main# cd ../../build
root@ubuntu:/opt/math/build# ./main
[Mar  2 2021 00:13:53] add.c, add
3+2=5
[Mar  2 2021 00:14:22] sub.c, sub
3-2=1
[Mar  2 2021 00:14:46] mul.c, mul
3*2=6
[Mar  2 2021 00:15:19] square_diff.c, square_diff
[Mar  2 2021 00:13:53] add.c, add
[Mar  2 2021 00:14:22] sub.c, sub
3^2-2^2=5
root@ubuntu:/opt/math/build# 

2.4 查看目标文件和函数调用

进入build/lib路径,执行下列指令,过程如下。

  • ar t libselfmath.a (查看库文件中的目标文件)
  • nm libselfmath.a (查看库文件中的函数调用)
root@ubuntu:/opt/math/build/lib# ar t libselfmath.a 
add.o
mul.o
sub.o
square_diff.o
root@ubuntu:/opt/math/build/lib# 
root@ubuntu:/opt/math/build/lib# nm libselfmath.a 

add.o:
0000000000000000 T add
000000000000002b r __func__.2263
                 U _GLOBAL_OFFSET_TABLE_
                 U printf

mul.o:
000000000000002b r __func__.2263
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T mul
                 U printf

sub.o:
000000000000002b r __func__.2263
                 U _GLOBAL_OFFSET_TABLE_
                 U printf
0000000000000000 T sub

square_diff.o:
                 U add
0000000000000038 r __func__.2263
                 U _GLOBAL_OFFSET_TABLE_
                 U printf
0000000000000000 T square_diff
                 U sub
root@ubuntu:/opt/math/build/lib# 

2.5 提取目标文件

进入build/lib路径,执行下列指令,提取库文件中的目标文件,过程如下。

  • ar x libselfmath.a
root@ubuntu:/opt/math/build/lib# ar x libselfmath.a 
root@ubuntu:/opt/math/build/lib# ls
add.o  libselfmath.a  mul.o  square_diff.o  sub.o
root@ubuntu:/opt/math/build/lib# 

2.6 添加和删除目标文件

进入build/lib路径,执行下列指令,过程如下。

  • ar d libselfmath.a add.o (从库文件中删除add.o目标文件)
  • ar r libselfmath.a add.o (向库文件中添加add.o目标文件)
root@ubuntu:/opt/math/build/lib# ar d libselfmath.a add.o 
root@ubuntu:/opt/math/build/lib# ar t libselfmath.a 
mul.o
sub.o
square_diff.o
root@ubuntu:/opt/math/build/lib# ar r libselfmath.a add.o 
root@ubuntu:/opt/math/build/lib# ar t libselfmath.a 
mul.o
sub.o
square_diff.o
add.o
root@ubuntu:/opt/math/build/lib# 

2.7 查看库依赖关系

进入build路径,执行下列指令,查看库依赖关系,过程如下。

  • ldd main
root@ubuntu:/opt/math/build# ldd main 
	linux-vdso.so.1 (0x00007ffc62fe1000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f54430e9000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f54436dc000)
root@ubuntu:/opt/math/build# 

3. 共享库的使用

动态库(共享库)也是一组目标文件(*.o)的集合,相对于静态库具有如下的优缺点。

  • 优点:动态函数库所产生的可执行文件比较小,因为其在编译的时候并没有被编译进目标代码中,程序执行到相关函数时才调用该函数库里的相应函数;动态函数库的改变不影响程序,所以动态函数库的升级比较方便;
  • 缺点:由于动态函数库没有被整合进程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库,Linux系统有几个重要的目录存放函数库,即/lib和/usr/lib。

接下来,从具体的实例入手阐述动态库的相关操作,实例代码同样参见附录A,不做赘述。

3.1 生成目标文件

进入src/selfmath路径,执行下列指令,在build/obj路径下会生成相应的目标文件。

  • gcc -Wall -c -fPIC add.c -o …/…/build/obj/add.o -I…/…/inc
  • gcc -Wall -c -fPIC sub.c -o …/…/build/obj/sub.o -I…/…/inc
  • gcc -Wall -c -fPIC mul.c -o …/…/build/obj/mul.o -I…/…/inc
  • gcc -Wall -c -fPIC square_diff.c -o …/…/build/obj/square_diff.o -I…/…/inc
root@ubuntu:/opt/math# cd src/selfmath
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c -fPIC add.c -o ../../build/obj/add.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c -fPIC sub.c -o ../../build/obj/sub.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c -fPIC mul.c -o ../../build/obj/mul.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# gcc -Wall -c -fPIC square_diff.c -o ../../build/obj/square_diff.o -I../../inc
root@ubuntu:/opt/math/src/selfmath# 
root@ubuntu:/opt/math/src/selfmath# tree ../../
../../
├── build
│   ├── lib
│   └── obj
│       ├── add.o					// 由add.c和selfmath.h生成的目标文件
│       ├── mul.o					// 由mul.c和selfmath.h生成的目标文件
│       ├── square_diff.o			// 由square_diff.c和selfmath.h生成的目标文件
│       └── sub.o					// 由sub.c和selfmath.h生成的目标文件
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath					// 执行命令路径
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 10 files
root@ubuntu:/opt/math/src/selfmath#

3.2 创建动态库

进入build/obj路径,执行下列指令,在build/lib路径下会生成相应的动态库。

  • gcc -Wall -shared -fPIC -o …/lib/libselfmath.so add.o mul.o sub.o square_diff.o

其中选项-shared和-fPIC简要解释如下:

  • -shared:指定生成动态链接库(让链接器生成T类型的导出符号,有时候也生成弱连接W类型的导出符号),如果不使用该选项,外部程序无法链接,相当于一个可执行文件。
  • -fPIC:作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)。即产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载到内存的任意位置。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
root@ubuntu:/opt/math/src/selfmath# cd ../../build/obj
root@ubuntu:/opt/math/build/obj# gcc -Wall -shared -fPIC -o ../lib/libselfmath.so add.o mul.o sub.o square_diff.o
root@ubuntu:/opt/math/build/obj# 
root@ubuntu:/opt/math/build/obj# tree ../../
../../
├── build
│   ├── lib
│   │   └── libselfmath.so			// 由add.o、mul.o、sub.o和square_diff.o生成的动态库
│   └── obj							// 执行命令路径
│       ├── add.o
│       ├── mul.o
│       ├── square_diff.o
│       └── sub.o
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 11 files
root@ubuntu:/opt/math/build/obj#

3.3 使用动态库

进入src/main路径,执行下列指令,在build路径下会生成最终的可执行文件。

  • gcc -Wall main.c -o …/…/build/main -I…/…/inc -L…/…/build/lib -lselfmath
root@ubuntu:/opt/math/build/obj# cd ../../src/main/
root@ubuntu:/opt/math/src/main# gcc -Wall main.c -o ../../build/main -I../../inc -L../../build/lib -lselfmath
root@ubuntu:/opt/math/src/main# 
root@ubuntu:/opt/math/src/main# tree ../../
../../
├── build
│   ├── lib
│   │   └── libselfmath.so
│   ├── main						// 由main.c、selfmath.h、libselfmath.so生成的可执行文件
│   └── obj
│       ├── add.o
│       ├── mul.o
│       ├── square_diff.o
│       └── sub.o
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 12 files
root@ubuntu:/opt/math/src/main#

3.4 加载动态库

执行下列指令运行程序,报错./main:error while loading shared libraries: libselfmath.so: cannot open shared object file: No such file or directory。显然,加载动态库失败,下面继续介绍一下动态库加载问题。

  • ./main
root@ubuntu:/opt/math/build# ./main
./main: error while loading shared libraries: libselfmath.so: cannot open shared object file: No such file or directory
root@ubuntu:/opt/math/build#
3.4.1 系统自动加载动态库

程序在运行时,GNU链接器会依次在如下标准系统函数目录查找需要的动态库并载入内存。

  1. ELF文件的DT_RPATH
    后续专门另起博文研究ELF文件,这里暂不做赘述。
  2. 环境变量LD_LIBRARY_PATH(动态库查找路径)
    执行export LD_LIBRARY_PATH=/opt/math/build/lib,程序便运行正常。当然,该命令仅仅是临时设置环境变量 LD_LIBRARY_PATH,重启后一切设置将不复存在。如果想把/opt/math/build/lib持续设置到LD_LIBRARY_PATH,可以在~/.bashrc或 ~/.bash_profile或/etc/profile加入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/math/build/lib语句。至于三者的区别,后续专门另起博文研究,这里暂不做赘述。
root@ubuntu:/opt/math/build# export LD_LIBRARY_PATH=/opt/math/build/lib
root@ubuntu:/opt/math/build# echo $LD_LIBRARY_PATH
/opt/math/build/lib
root@ubuntu:/opt/math/build# ./main
[Mar  6 2021 02:08:21] add.c, add
3+2=5
[Mar  6 2021 02:08:29] sub.c, sub
3-2=1
[Mar  6 2021 02:08:36] mul.c, mul
3*2=6
[Mar  6 2021 02:09:24] square_diff.c, square_diff
[Mar  6 2021 02:08:21] add.c, add
[Mar  6 2021 02:08:29] sub.c, sub
3^2-2^2=5
root@ubuntu:/opt/math/build#
  1. /etc/ld.so.cache文件列表
    在/etc/ld.so.conf文件中加一行/opt/math/build/lib,保存过后执行ldconfig,程序便运行正常。ldconfig命令的用途:在默认搜寻目录(/usr/lib和/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式lib*.so*),进而创建动态装入程序(ld.so)所需的链接和缓存文件(/etc/ld.so.cache),此文件保存已排好序的动态链接库名字列表。此方法设置较为麻烦,但是可以被系统共享,不受用户的限制。
  2. /lib和/usr/lib目录
    执行cp ./lib/libselfmath.so /usr/lib或cp ./lib/libselfmath.so /lib,将libselfmath.so拷贝到相应目录,程序便运行正常。
3.4.2 应用程序加载动态库

在应用程序中使用dlopen()和dlsym()函数,定义在dlfcn.h头文件。该函数将打开一个新库,并把它装入内存。后续专门另起博文研究dlopen()和dlsym()函数的使用,这里暂不做赘述。

3.5 查看库依赖关系

进入build路径,执行下列指令,查看库依赖关系,过程如下。

  • ldd main
root@ubuntu:/opt/math/build# ldd main
	linux-vdso.so.1 (0x00007ffe0c6f8000)
	libselfmath.so => /usr/lib/libselfmath.so (0x00007f5992eed000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5992afc000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f59932f1000)
root@ubuntu:/opt/math/build#

4. 同名静态库和动态库优先级问题

无论是静态库还是动态库,编译时使用的GCC命令一样,那么当静态库和动态库同名时,GCC命令会使用哪个库文件呢?如下所示,创建静态库和动态库并编译执行,从输出结果得出结论:当静态库和动态库同名时, GCC命令将优先使用动态库。

root@ubuntu:/opt/math/build/obj# tree ../../
../../
├── build
│   ├── lib							// 静态库和动态库同名
│   │   ├── libselfmath.a
│   │   └── libselfmath.so
│   ├── main
│   └── obj
│       ├── add.o
│       ├── mul.o
│       ├── square_diff.o
│       └── sub.o
├── inc
│   └── selfmath.h
└── src
    ├── main
    │   └── main.c
    └── selfmath
        ├── add.c
        ├── mul.c
        ├── square_diff.c
        └── sub.c

7 directories, 13 files
root@ubuntu:/opt/math/build/obj# cd ../../src/main/
root@ubuntu:/opt/math/src/main# gcc -Wall main.c -o ../../build/main -I../../inc -L../../build/lib -lselfmath
root@ubuntu:/opt/math/src/main# cd ../../build/
root@ubuntu:/opt/math/build# ./main
./main: error while loading shared libraries: libselfmath.so: cannot open shared object file: No such file or directory
root@ubuntu:/opt/math/build# 

默认情况下,GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库。如果需要的话,可以在编译时加上-static选项,强制使用静态链接库,如下所示。

root@ubuntu:/opt/math/src/main# gcc -Wall -static main.c -o ../../build/main -I../../inc -L../../build/lib -lselfmath
root@ubuntu:/opt/math/src/main# cd ../../build
root@ubuntu:/opt/math/build# ./main
[Mar  6 2021 02:08:21] add.c, add
3+2=5
[Mar  6 2021 02:08:29] sub.c, sub
3-2=1
[Mar  6 2021 02:08:36] mul.c, mul
3*2=6
[Mar  6 2021 02:09:24] square_diff.c, square_diff
[Mar  6 2021 02:08:21] add.c, add
[Mar  6 2021 02:08:29] sub.c, sub
3^2-2^2=5
root@ubuntu:/opt/math/build# 

附录A 函数库实操源码实例

A.1 源码结构

root@ubuntu:/opt/math# tree ./
./
├── build					// 存储编译结果路径
│   ├── lib						// 存储静态库(.a)路径,由./build/obj下的目标文件(.o)生成
│   └── obj						// 存储目标文件(.o)路径,由./src/selfmath下的源文件(.c)生成
├── inc						// 存储头文件(.h)路径
│   └── selfmath.h				// 头文件,声明了./src/selfmath中源文件的函数等,和静态库(.a)一同被main使用
└── src						// 源文件(.c)路径
    ├── main					// 主函数源文件(.c)路径
    │   └── main.c					// 主函数文件,调用静态库(.a)中的函数
    └── selfmath				// 静态库源文件(.c)路径
        ├── add.c					// 加法运算文件
        ├── mul.c					// 乘法运算文件
        ├── square_diff.c			// 平方差运算文件,调用了add.c和sub.c中的函数
        └── sub.c					// 减法运算文件

7 directories, 6 files
root@ubuntu:/opt/math# 

A.2 源码实现

  • add.c源码
// File: add.c
#include "selfmath.h"
int add(int a, int b)
{
	printf("[%s %s] %s, %s\n", __DATE__, __TIME__, __FILE__, __func__);
	return a+b;
}
  • sub.c源码
// File: sub.c
#include "selfmath.h"
int sub(int a, int b)
{
        printf("[%s %s] %s, %s\n", __DATE__, __TIME__, __FILE__, __func__);
        return a-b;
}
  • mul.c源码
// File: mul.c
#include "selfmath.h"
int mul(int a, int b)
{
	printf("[%s %s] %s, %s\n", __DATE__, __TIME__, __FILE__, __func__);
	return a*b;
}
  • square_diff.c源码
// File: square_diff.c
#include "selfmath.h"
int square_diff(int a, int b)
{
	printf("[%s %s] %s, %s\n", __DATE__, __TIME__, __FILE__, __func__);
	return add(a, b) * sub(a, b);
}
  • main.c源码
// File: main.c
#include "selfmath.h"
int main(void)
{
	int res;
	res = add(3, 2);
	printf("3+2=%d\n", res);
	res = sub(3, 2);
	printf("3-2=%d\n", res);
	res = mul(3, 2);
	printf("3*2=%d\n", res);
	res = square_diff(3, 2);
	printf("3^2-2^2=%d\n", res);
	return 0;
}
  • selfmath.h源码
// File: selfmath.h
#ifndef _SELF_MATH_H_
#define _SELF_MATH_H_

#include <stdio.h>
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int square_diff(int a, int b);

#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值