静态库和动态库

静态库和动态库对比

静态库

一些目标代码的集合。按照习惯,linux中一般一.a作为文件名后缀。使用ar(archiver)命令可以创建静态库。

在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。

静态库在应用程序生成时,可以不必再编译,节省编译时间。

静态库会占用大量存储空间。

静态库

动态库

在执行程序启动时加载到执行程序中,可以被多个执行程序共享使用。

动态库不需要编译入程序, 运行时动态加载, 导致速度慢了一些

动态库

二者的适合场景:

  • 静态库: 对空间要求较低, 对时间要求较高
  • 动态库: 对时间要求较低, 对空间要求较高

静态库制作

先用gcc的-c参数将源文件编译成二进制文件, 再用ar命令封装静态库

1
2
3
4
5
6
# 有文件add.c div1.c sub.c
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c div1.c -o div1.o

ar rcs libMyMath.a add.o sub.o div1.o

使用:

1
2
# 将库直接加入编译的源文件中即可使用
gcc test.c libMyMath.a -o test1

静态库使用及头文件对应

隐式声明: 编译过程中没有遇到函数定义和函数声明, 编译器会帮助做隐式声明;

但是这种隐式声明只能对于返回值为int型的;

解决方法:

1
2
3
4
5
6
7
8
9
/*添加头文件,防止头文件重复包含,一旦头文件被展开过一次,_MYMATH_H_就被定义过了,后面就不会再展开*/
#ifndef _MYMATH_H_
#define _MYMATH_H_

int add(int,int);
int sub(int,int);
int div1(int,int);

#endif

然后将源文件和库联编即可, 注意源文件在前

1
2
# 动态库存放在~/sys/staticLib/lib
~/sys/staticLib$ gcc test.c ./lib/libMyMath.a -o test -I ./inc

动态库制作

生成与位置无关的代码

将源文件.c编译为目标文件.o, 生成与位置无关的代码, 借助参数-fPIC

动态库加载

编译生成hello.o的时候, 各个函数的地址还是相对于main的地址, 链接阶段填入main的地址;

由于动态库的函数在库里, 不能像程序内部的函数一样直接填入main的地址, 动态函数在a.out中没有位置, 依赖于@plt, 进行延迟绑定;

  • 查看二进制文件的反汇编代码:objdump -dS test

  • 输出重定向:objdump -dS test > test.s

制作演示:

1
2
3
4
5
6
7
8
# 1. 将.c文件生成.o文件(生成与位置无关的代码-fPIC):
gcc -c add.c -o add.o -fPIC
# 2. 使用gcc -shared制作动态库:
gcc -shared add.o sub.o div1.o -o libMyMath.so
# 3. 编译可执行程序时, 指定所使用的动态库, -l 指定库名, -L 指定库路径:
gcc test.c -o test -l MyMath -L ./lib
# 4. 运行可执行程序
./test # 报错(编译通过,执行错误,找不到文件)

动态库加载错误原因及解决办法

上面的错误原因:

  • 链接器:工作于链接阶段, 工作时需要指定-l和-L参数, 上面已经指定
  • 动态链接器:工作于程序运行阶段, 工作时需要提供动态库所在目录

上面两者没有任何关系

方法1:

  • 动态链接器要根据环境变量寻找动态库:LD_LIBRARY_PATH

  • 执行export LD_LIBRARY_PATH=./lib

  • 指定后就可以执行了(但是上面指定的只是临时的, 环境变量是进程的概念)

  • 要想永久指定, 需要更改配置文件, 加入环境变量, 重启终端使之生效:

1
2
# ~/.bashrc下加入
export LD_LIBRARY_PATH=./lib

方法2:

  • 像标准C库这种本身就在系统的环境变量里, 所以能找到;

  • 滥竽充数法:将库文件放到系统根目录下的lib里就可以了;

  • ldd test可以查看程序运行所需要的动态库

最后一种方法:修改配置文件法;

1
2
3
sudo vim /etc/ld.so.conf
# 写入动态库绝对路径, 保存;
sudo ldconfig -v #使配置文件生效

动态库和静态库共存时, 编译器优先使用动态库;