计算机的发展,离不开前人的一点点积累,让我们可以直接使用别人的轮子进行快速开发。库存在的意义,就是避免重复造轮子,对于开发好的重复可用的代码,就直接封装为库。
库一般分为两大类,一类是动态库,一类是静态库。
[TOC]
静态库、动态库的介绍以及对比
静态库
静态库,一般命名后缀为.a
。
会在编译阶段完全被整合进代码段中,所以生成的可执行性文件也比较大。
优点是:编译后的执行程序不再需要函数库的支持,因为所有要使用的函数己经被编译进去了。
这也是他的缺点:
- 生成可执行性文件体积大;
- 静态库如果发生了改变那么你的程序必须要重新编译。
动态库
动态库,一般命名后缀为.so
。
动态库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。
由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
gcc的编译流程
在介绍怎样编译出静态库和动态库之前,先介绍一下,gcc的编译过程,怎样把一个c、c++文件编译为可执行文件。
编译为可执行的文件,一般需要四个流程:
- 预处理(preprocessing)
- 编译(compilation)
- 汇编(assembly)
- 链接(linking)
gcc编译参数
1 | 常用选项 |
注意:-o
为输出指定文件名,这个命名不影响文件属性,如下面的预处理阶段,如果改为➜ gcc -E main.c -o main.o
,也可能正常输出。但是,文件属性仍然只是进行预处理后的文件,而非可执行文件,不同后缀只是为了辨识不同阶段而已。
预处理(preprocessing)
先创建一个main.c
文件
1 |
|
1 | ➜ gcc -E main.c -o main.i |
预处理阶段主要处理include和define等。它把#include
包含进来的.h
文件插入到#include
所在的位置,把源程序中使用到的用#define
定义的宏用实际的字符串代替
下面列出了生成的部分内容,可以看到stdio.h
的内容被插入进来
1 | ......省略...... |
编译(compilation)
1 | ➜ gcc -S main.i -o main.s |
在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
同样列出部分内容,可以看到被转化为汇编语言
1 | _main: ## @main |
汇编(assembly)
1 | ➜ gcc -c main.s -o main.o |
汇编阶段把.s
汇编语言文件翻译成二进制机器指令文件.o
链接(linking)
1 | ➜ gcc main.s -o main |
链接阶段,链接的是函数库。
在main.c
中并没有定义printf
的函数实现,且在预编译中包含进的stdio.h
中也只有该函数的声明。系统把这些函数实现都放到名为libc.so
的动态库,所以在这个阶段,gcc默认去系统默认路径/usr/local/lib
搜索动态库并进行链接,就可以生成可执行文件。
1 | 执行可执行文件,输出结果 |
四个阶段一并执行
上面的为分阶段介绍的过程,一般我们只是执行gcc
命令,会包含以上四个阶段
1 | ➜ gcc main.c -o main2 |
查看引用库
上面链接阶段,说到默认会去搜索本地的动态库,通过otool
可以查看引用的库,linux为ldd
命令
1 | 可以看到引用库id为libSystem.B.dylib,地址为/usr/lib |
去到/usr/lib
目录下,可以看到libSystem.B.dylib
文件,再执行otool
可以看到详细引用库id
1 | ➜ otool -L ./libSystem.B.dylib |
.a .so .o 文件的区别
通过上面的介绍,应该大概知道,.a
、.so
、.o
文件的区别了,如果还不太理解,可以参考浅析Linux中的.a、.so、和.o文件这篇文章的介绍,通过window的文件去类比介绍
怎样编译出静态库和动态库
前面介绍不同后缀文件的区别作为铺垫,接下来,就来到这篇文章的主题
创建静态库
1 | ar cr 生成的静态库名称.a 引用的可执行文件.o [引用的可执行文件.o] |
c:创建一个库。不管库是否存在,都将创建。
r:在库中插入模块(替换)
创建动态库
1 | gcc -shared -fPCI 可执行文件.o [可执行文件.o] -o 生成的动态库名称.so |
实例介绍
接下来,举例详细介绍怎样创建静态库和动态库,并且对比两者的不同点
创建使用到的程序
hello.h文件
1 |
|
hello.c文件
1 |
|
main.c文件
1 |
|
把hello.c编译成.o文件
无论静态库,还是动态库,都是由.o
文件创建的。因此,我们必须将源程序hello.c
通过gcc先编译成.o
文件
1 | ➜ gcc -c hello.c -o hello.o |
创建、使用静态库
创建静态库
通过.o
可执行文件,创建静态库libhello.o
1 | ➜ ar cr libhello.a hello.o |
使用静态库
使用静态库,编译main.c
源文件,生成hello
可执行文件
1 | ➜ gcc -o hello main.c -L. -lhello |
删除libhello.a
文件,验证是否hello.c
文件中的实现被直接链接到hello
中
1 | ➜ rm libhello.a |
创建、使用动态库
创建动态库
1 | ➜ gcc -shared -fPIC -o libhello.so hello.o |
使用动态库
1 | ➜ gcc -o hello main.c -L. -lhello |
把生成的libhello.so
移动到其他路径,再执行
1 | 提示加载不到libhello.so库,找不到hello的实现 |
mac os上,如果有通过-L -l
去指定路径,那么会优先在指定的路径下找,如果找不到,会去默认路径/usr/local/lib
找,可以尝试把libhello.so
文件移动到默认路径中,再次执行
1 | ➜ mv libhello.so /usr/local/lib |
参考资料
-
可以参考其gcc编译的属性值
编译过程
-
也可以结合这篇看一下编译的过程,生成不同格式的文件,该文章还介绍了静态库和动态库的区别
-
写得非常好,一步步介绍生成.o文件,再分别介绍.a和.so文件,并且最后还验证使用两种库的区别
-
-
编译为.a 和 .so文件
-
结合window介绍几个文件的区别
-
主要介绍so的知识点,也可以结合之前的知识点看