目录
前言
Part1. 库的意义
Part2. 静态库制作、编译与发布
Part2.1. 静态库制作步骤
Part2.2. 使用静态库编译业务代码
Part2.3. 静态库的打包分发
Part2.4. Makefile 自动化构建静态库、一键打包发布
Part3. 动态库制作、编译、部署
Part3.1. 动态库制作
Part3.2. 编译业务程序链接动态库
Part3.3. 解决运行时找不到动态库的方案
Part3.4. 强制链接静态库
Part4. 静态库与动态库对比
Part5. 补充拓展
Part6. 结语
前言
Linux下对于动静态库的认识是非常重要的,接下来来跟随小编的视角来看看吧。
let's go!!!!!!!!
Part1. 库的意义
在工程开发中,我们会把可复用的功能封装成库,不需要每次都重新编译源码。
• 源码不需要对外交付,只需要交付头文件(.h,用于给用户声明函数) +库文件(.a / .so,存放编译好的机器指令)
•多个项目可以复用同一套库,大幅减少重复编译的成本
• 版本迭代时,只需要更新库文件,业务代码无需改动
补充:目标文件 .o 的价值:一个大型工程有上百个 .c 文件,如果只修改其中一个源码,只需要重新编译这一个文件生成 .o,其余目标文件可以直接复用,避免全量重新编译,节省编译时间。编译链路:.c → 编译生成 .o(可重定位目标文件,ELF格式) → 链接 → 可执行程序 / 库
底层小知识:.o、静态库、动态库、可执行程序,全部都是ELF格式。ELF内部由大量的section(段)组成:代码段、数据段、只读段等;运行时操作系统会把多个section合并为segment(段分区),映射到进程的虚拟地址空间。
Part2. 静态库制作、编译与发布
静态库后缀命名规范:libxxx.a,比如我们示例库名字为 libmyc.a
Part2.1. 静态库制作步骤
1. 先把源码编译为目标文件 .o
# -c:只编译,不链接,生成 *.o gcc -c 1.c -o 1.o gcc -c 2.c -o 2.o
2. 使用 ar 工具打包多个 .o,归档成为静态库 .a# ar -rc :r插入/替换,c创建归档文件 ar -rc libmyc.a 1.o 2.o
ar 本质就是一个归档工具,把一堆 .o 打包成一个文件,静态库本身就是一堆目标文件的压缩包,gcc链接的时候会把库里面用到的目标代码直接拷贝进最终的可执行文件。Part2.2. 使用静态库编译业务代码
假设业务代码 main.c 需要调用我们静态库里面的函数。
编译链接语法:gcc -o exe main.c -I ./include -L ./mylib -l myc
参数解释:
参数 作用
-I路径 指定头文件搜索路径,用来找到 .h
-L路径 指定库文件(.a/.so)的搜索路径
-lxxx 链接名为 libxxx.a / libxxx.so 的库,自动补全lib前缀和后缀Part2.3. 静态库的打包分发
我们交付给用户的时候,不需要交付 .c 源码,只需要交付:
• include/:存放 .h 头文件(函数声明)
• mylib/:存放编译好的 libmyc.a 静态库
用户拿到之后,有两种方式使用头文件:1. 编译时通过 -I 指定头文件目录
gcc -c main.c -I ./lib/include
2. 或者直接把头文件拷贝到系统标准头文件目录 /usr/includecp lib/include/* /usr/include
库文件拷贝到系统库目录 /usr/libcp lib/mylib/libmyc.a /usr/lib
之后编译就可以简化,不需要写 -I -L,直接:gcc -o exe main.c -l mycPart2.4. Makefile 自动化构建静态库、一键打包发布
可以用Makefile自动化完成编译、打包、创建目录、打包压缩交付包
libmyc.a: 1.o 2.o ar -rc $@ $^ 1.o:1.c gcc -c $< -o $@# 发布目标:自动创建目录,拷贝头文件、库,打包tar压缩包
.PHONY: output output: mkdir -p lib/include mkdir -p lib/mylib cp -f *.h lib/include cp -f libmyc.a lib/mylib tar -czf lib.tgz lib
执行make output,直接生成 lib.tgz,这个压缩包就可以发给别人使用。
静态库核心特点:链接的时候,代码直接复制进exe。一旦编译完成,后续静态库文件就算删除,程序依旧可以正常运行。
Part3. 动态库制作、编译、部署
动态库后缀:.so,命名示例 libmyc.so
动态库不会把代码拷贝进可执行文件,程序运行的时候,操作系统才去加载 .so,多个进程可以共享同一份动态库在内存中的副本,节省内存。Part3.1. 动态库制作
1. 编译目标文件,必须带上 -fPIC,生成位置无关代码
gcc -fPIC -c 1.c -o 1.o gcc -fPIC -c 2.c -o 2.o
-fPIC:Position Independent Code 位置无关代码,生成的机器指令没有硬编码绝对地址,可以被加载到虚拟地址空间的任意位置,是动态库的核心。静态库不需要 -fPIC。
2. 使用 -shared 参数,把 .o 链接打包为动态库 .sogcc -shared -o libmyc.so 1.o 2.oPart3.2. 编译业务程序链接动态库
编译链接阶段语法和静态库几乎一模一样:
gcc -o exe main.c -I ./include -L ./mylib -l myc
这里有一个非常经典的坑:编译链接成功,但是运行的时候报错,找不到动态库!
原因:-L 只是编译阶段告诉gcc去哪里找库;程序运行阶段是操作系统去加载动态库,和gcc已经无关了,操作系统有自己一套动态库搜索路径。Part3.3. 解决运行时找不到动态库的方案
方案1:临时环境变量 LD_LIBRARY_PATH
LD_LIBRARY_PATH 是Linux操作系统的动态库环境变量,操作系统运行程序的时候,会优先从这个变量记录的目录里面搜索 .so。
# 临时生效,当前终端窗口有效,关闭终端就失效 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xxx/lib/mylib ./exe
方案2:写入系统动态库配置文件(永久生效)Linux系统动态链接器会读取 /etc/ld.so.conf.d/ 下面的配置文件
1. 在 /etc/ld.so.conf.d/ 新建一个配置文件,随便命名,例如 myconfig.conf,写入我们动态库的绝对路径
/home/xxx/lib/mylib2.更新动态链接器缓存ldconfig
永久生效,所有用户、所有终端都可以找到这个动态库
方案3:直接拷贝动态库到系统标准库目录/usr/libcp libmyc.so /usr/lib ldconfigPart3.4. 强制链接静态库
如果你本身系统同时存在 .a 和 .so,gcc默认优先链接动态库。
如果你想要强制编译的时候使用静态库,增加 -static 参数gcc -o exe main.c -I ... -L ... -l myc -static
Part4. 静态库与动态库对比
特性静态库 .a动态库 .so
链接时期:编译链接阶段,代码拷贝进可执行文件编译阶段只记录符号,运行时操作系统加载
程序体积:可执行文件更大,包含库代码可执行文件更小
内存占用:每个进程都有一份独立的库代码副本,内存占用高内存中只加载一份,多进程共享,节省内存
更新方式:更新库,业务代码必须重新编译链接替换 .so 文件即可,不需要重新编译业务程序
依赖:编译完成后不再依赖原 .a 文件运行时必须保证 .so 存在,否则程序无法启动工程选型:Linux后端服务绝大多数场景优先使用动态库;一些嵌入式、想要单文件交付、不想携带一堆so的场景会使用静态库。
Part5. 补充拓展
1. .o 可重定位目标文件:编译产物,还没有分配虚拟地址,等待链接器完成地址重定位
2. ELF 文件内部划分为很多 section:.text代码段、.data数据段、.rodata只读段、.bss未初始化数据段
3. 当程序被操作系统加载运行时,内核会把多个section合并成segment内存段,映射到进程独立的虚拟地址空间,这就是进程的内存布局。
Part6. 结语
这篇文章我们深入的知道了动静态库的相关知识,接下来,小编还会带来更多Linux相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!
最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~