C语言底层原理(二):动态库、静态库

1,841 阅读4分钟
原文链接: www.jianshu.com

库的概念

库:实现了某一类功能的若干函数和二进制代码的集合。库的后缀名在不同平台上表现不同:
Windows:静态库:xxx.lib && 动态库:xxx.dll
Linux:静态库:xxx.a && 动态库:xxx.so

库分为静态库和动态库,与之对应的操作是静态链接和动态链接,静态库不能采用动态链接,同理,动态库也不能采用静态链接。

静态库:在链接的时候,函数库被完整的拷贝到可执行文件中,对应的链接方式成为静态链接,采用gcc -static指令。这里的可执行文件可以使单纯的一个a.out,也可以是一整个app或者程序。

动态库:相对于静态库,动态库并没有将库中所有的数据复制到可执行文件中,在程序运行的时候才会被动态载入。

备注
动态链接可以理解成只是将声明文件复制到了程序中。在运行时根据预先设置的动态库的位置和这部分声明来调用对应的库。所以说动态库也叫共享库,共享在整个系统中。对于iOS系统而言,所有系统提供的.framework都是动态库且在各个app之间共享。但是iOS基于沙盒模式,其不允许用户自己创建动态的.framework。即使在iOS8之后可以创建动态framework,但其本质还是将动态库放入了app的沙盒中。iOS不允许包含自定义动态库的app上架到app store,所以iOS中的动态库只能应用在不上架app store的企业应用中。

静态库和动态库的区别和特点

静态库:
1、一旦链接完成,执行程序就和函数库没有任何关联
2、占用空间和资源,拷贝多次就会占用多份资源
3、会导致升级不便,一个地方修改就需要全量更新

动态库:
1、可以实现进程之间的资源共享
2、动态库把对一些库函数的链接载入推迟到程序运行的时期
3、将一些程序升级变得简单
4、可以通过显示调用做到链接载入完全由程序员在程序代码中控制
5、动态库的创建直接使用编译器即可创建动态库,不需要打包工具(ar、lib.exe

示例代码

文件夹结构:



mymath.h文件:

// mymath.h
#ifndef MYMATH_H
#define MYMATH_H
int xkadd(int a, int b);
int xksum(int a, int b);
#endif

mymath.c文件:

// mymath.c
int xkadd(int a, int b){
    return a+b;
}
int xksum(int a, int b){
    return a-b;
}

test.c文件:

// test.c
#include <stdio.h>
#include "mymath.h"// 自定义头文件
int main(){
    int a = 2;
    int b = 3;
    int sum = xkadd(a, b);
    printf("a=%d, b=%d, a+b=%d\n", a, b, sum);
}

静态库的生成和使用

// 进入函数库所在文件夹
caoxkdembp:static-lib caoxk$ cd /Users/caoxk/Demo/static-lib/lib 
// 将函数库编译成.o文件
caoxkdembp:lib caoxk$ gcc -c mymath.c
// 将.o文件使用ar工具生成.a静态库
caoxkdembp:lib caoxk$ ar -crv libmymath.a mymath.o
// 这一行是上一行命令执行结果的输出
a - mymath.o
caoxkdembp:lib caoxk$ cd ..
caoxkdembp:static-lib caoxk$ gcc test.c -L ./lib/ -lmymath
// 未告知编译器对应的头文件地址,执行命令后报错
test.c:4:10: fatal error: 'mymath.h' file not found
#include "mymath.h"// 自定义头文件
         ^~~~~~~~~~
1 error generated.
// 使用-I执行告诉编译器libmymath.a对应的头文件所在的地址
caoxkdembp:static-lib caoxk$ gcc test.c -L ./lib/ -lmymath -I ./lib/
// 执行可执行文件
caoxkdembp:static-lib caoxk$ ./a.out 
a=2, b=3, a+b=5

最终的文件夹结构:


Static-Lib End

总结
静态库的使用需要结合头文件,比如上面直接gcc主文件和mymath库时,没有使用-I path告知头文件的地址,所以会找不到头文件。而iOS中的framework其实包含三种文件:.h头文件、.a静态库、资源文件,如图片等

动态库/共享库的生成

caoxkdeMacBook-Pro:lib caoxk$ cd /Users/caoxk/Demo/dynamic-lib 
caoxkdeMacBook-Pro:dynamic-lib caoxk$ ls
lib test.c
caoxkdeMacBook-Pro:dynamic-lib caoxk$ cd lib/
// 生成费共享的目标文件.o
caoxkdeMacBook-Pro:lib caoxk$ gcc -c -fpic mymath.c
// 使用gcc -shared的第二层意义来生成动态链接库
caoxkdeMacBook-Pro:lib caoxk$ gcc -shared -o libmymath.so mymath.o
caoxkdeMacBook-Pro:lib caoxk$ cd ..
caoxkdeMacBook-Pro:dynamic-lib caoxk$ pwd
/Users/caoxk/Demo/dynamic-lib
// -I:在./lib中寻找头文件;-L:在./lib中寻找;lmymath:连接libmymath.so动态库
caoxkdeMacBook-Pro:dynamic-lib caoxk$ gcc test.c -I ./lib/ -L ./lib/ -lmymath
caoxkdeMacBook-Pro:dynamic-lib caoxk$ ./a.out 
dyld: Library not loaded: libmymath.so
  Referenced from: /Users/caoxk/Demo/dynamic-lib/./a.out
  Reason: image not found
Abort trap: 6
caoxkdeMacBook-Pro:dynamic-lib caoxk$

以上执行后,找不到动态库。因为mac中不能使用ldd指令,所以使用otool -L指令来查询执行文件中动态库的路径,如下:

caoxkdeMacBook-Pro:dynamic-lib caoxk$ ldd a.out 
-bash: ldd: command not found
caoxkdeMacBook-Pro:dynamic-lib caoxk$ otool -L a.out 
a.out:
    libmymath.so (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5)
caoxkdeMacBook-Pro:dynamic-lib caoxk$

以上结果表明:存在两个库,一个是文件路径为/usr/lib/libSystem.B.dylib的库,另一个是没有路径的libmymath.so库,也就是我们自定义的动态库没有设置路径,或者说是和a.out同级,将libmymath.so复制到a.out同级目录后运行:

caoxkdeMacBook-Pro:dynamic-lib caoxk$ cp lib/libmymath.so ./
caoxkdeMacBook-Pro:dynamic-lib caoxk$ ./a.out 
a=2, b=3, a+b=5
caoxkdeMacBook-Pro:dynamic-lib caoxk$

那么这个问题如何解决呢?首先我们知道动态库的寻址顺序是:
1、编译目标代码时指定的动态库搜索路径
2、环境变量LD_LIBRARY_PATH指定的动态库搜索路径
3、配置文件/etc/ld.so.conf中指定的动态库搜索路径
4、默认的动态库搜索路径/lib
5、默认的动态库搜索路径/usr/lib

也就是说我们需要告诉ld我们自定义的动态库的地址。这里引入一个指令:-Wl,-rpath=xxx(mac 中需要使用-Wl,rpath xxx或者-Wl,rpath,dir1,dir2,dir3)。这个指令的意思是指定runtime时的动态库寻址地址。而-L xxx是指定链接时库的寻址地址。

网上有几种方法:
1、将自动以的动态库复制到到/usr/lib中(权限不允许)
2、使用export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:《your_lib_path》设置临时增加链接动态库的路径(无效)
3、通过修改配置文件/etc/ld.so.conf中指定的动态库搜索路径,然后执行ldconfig命令来改变(未测试)
4、使用-Wl -rpath(无效)

所以这种情况如何解决?-WL -rpath的正确用法是什么?问题暂时留存

动态库和静态库相关的指令

-fpic:生成非共享模式的目标文件
-static:一般来讲,很多函数库会有静态库和动态库两个版本。这个-static是编译阶段的链接选项,只在链接时不链接动态库,全部链接静态库,所以生成的可执行文件会比较大。
-shared:这个指令有两个意思。不作为链接选项时,是告诉gcc生成一个动态库,使用格式是gcc -shared xxx.o -o libxxx.so 。作为编译选项时,和-static对立,尽量使用动态库,只有在没有动态库时才链接静态库。是默认选项
比如:

// 生成非共享模式的机器码文件
caoxkdembp:lib caoxk$ gcc -fpic -c mymath.c
// 生成动态库
caoxkdembp:lib caoxk$ gcc -o libmymath.so mymath.o
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

这里报错的原因是不加-shared,gcc默认执行的指令时编译并链接最终生成可执行文件,而mymath.c是一个函数库,当然没有main函数。没用main函数当然不能生成可执行文件

更多文章