总结:于是这里我们学习了三个环境变量
PATH shell 找可执行程序
LIBRARY_PATH gcc 找库
LD_LIBRARY_PATH load (连接器) 找库
库文件
单一模型:
将程序中所有功能全部实现于一个单一的源文件内部。编译时间长,不易于维护和升级,不易于协作开发
分离模型:
将程序中的不同功能模块划分到不同的源文件中,缩短编译时间,易于维护和升级,易于协作开发
对多个目标文件的管理比较麻烦
将多个目标文件统一整理合成一个库文件
为何要把一个程序分成多个源文件,并由每个源文件编译生成独立的目标文件?
化整为零、易于维护、便于协作
为何要把多个目标合并成一个库文件?
集零为整、方便使用、易于复用
可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类
库文件一般指计算机上的一类文件,分两种,一种是静态库,另一种是动态库
静态库
静态库的本质就是将多个目标文件打包成一个文件
链接静态库就是将库中被调用的代码复制到调用模块中
静态库的拓展名是 .a 例如 : libxxx.a
以构建数学库为例,静态库的构建顺序如下:
A.编辑库的实现代码和接口声明
计算模块:calc.h calc.c
显示模块:show.h show.c
接口文件: math.h
B.编译成目标文件
gcc -c calc.c
gcc -c show.c
C.打包成静态库
ar -r libmath.a calc.o show.o
ar命令:
ar [选项] <静态库文件> <目标文件列表>
-r 将目标插入到静态库中,已存在则更新
-q 将目标文件追加到静态库尾
-d 从静态库中删除目标文件
-t 列表显示静态库中的目标文件
-x 将静态库展开为目标文件
静态库的使用
编辑库的使用代码
main.c
编译并链接静态库
直接连接静态库
gcc main.c libmath.a
用 -l 指定库名,用 -L指定库路径
gcc main.c -lmath -L
用 -l 指定库名,用LIBRARY_PATH环境变量指定库路径
export LIBRARY_PATH=$LIBRARY_PATH:.. //当库在当前文件上一级目录时
gcc main.c -lmath
和makefile的区别:
makefile 本质是脚本文件
记录编译连接的过程
libmath.a 本质是一个二进制文件
是代码的仓库和功能的集合
nm libmath.a 可以看一看二进制文件
vim calc.h
a//计算模块的头文件
#ifndef __CALC_H_
#define __CALC_H_
//加法函数
int add(int,int);
//减法函数
int sub(int,int);
#endif //__CALC_H_
vim calc.c
//计算模块实现
#include"calc.h"
//加法函数
int add(int x,int y){
return x + y;
}
//减法函数
int sub(int x,int y){
return x - y;
}
vim show.h
//显示模块头文件
#ifndef __HSOW_H_
#define __HSOW_H_
//显示函数 3 + 5 = 8 4 - 2 = 2
void show(int,char,int,int);
#endif //__HSOW_H_
vim show.c
//显示模块的实现
#include<stdio.h>
#include"show.h"
//显示函数
void show(int l,char op,int r,int res){
printf("%d %c %d = %d\n",l,op,r,res);
}
vim main.c
//调用模块
#include<stdio.h> //有没有这个应该都可以
#include"calc.h"
#include"show.h"
//#include"math.h"
int main(void){
int a = 123;
int b = 456;
show(a,'+',b,add(a,b));
show(a,'-',b,sub(a,b));
return 0;
}
此时,
使用 gcc -c calc.c gcc -c show.c
而后将目标文件打包成静态库
ar -r libmath.a show.o calc.o
而后直接链接动态库的代码为
gcc math.c libmath.a 默认生成a.out
回顾一下,觉得在main.c文件里要写多个头文件不紧麻烦而且不易处理,于是加入一个新的头文件
我们称之为 接口文件
vim math.h
//接口文件
#ifndef __MATH_H_
#define __MATH_H_
#include"calc.h"
#include"show.h"
#endif //__MATH_H_
这样main.c得头文件只需要一个就可以
//调用模块
#include"math.h"
int main(void){
int a = 123;
int b = 456;
show(a,'+',b,add(a,b));
show(a,'-',b,sub(a,b));
return 0;
}
而后,当打包成的静态库不在本文件目录时 (重要!!!)
此时,用 -l 指定库名,用 -L指定库路径
或者,先用LIBRARY_PATH环境变量指定库路径,而后再用 -l指定库名
虽然用export将局部环境变量变成了全局环境变量,但是因为每一个终端都有自己的环境变量表,所以可以使用上一章节的知识,去 .bashrc中添加这一路径,如下图,第二个为静态库路径的定义
动态库
1.动态库和静态库不同,链接动态库不需要将被调用的函数代码复制到包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行到这段指令时,会加载该动态库并寻找被调用函数的入口地址并执行之
2.如果动态库中的代码同时为多个进程所用,动态库在内存的实例仅需一份,为所有使用该库的进程所共享,因此动态库亦称共享库
3.动态库的拓展名是 .so 例libxxx.so
动态库的构建
第一步与静态库一样,calc.c calc.h show.h show.c math.h
第二步:编译成目标文件
gcc -c fpic calc.c
gcc -c fpic show.c
第三步:打包成动态库
gcc -shared calc.o show.o -o libmath.so
编译链接也可以合并成一步完成
gcc -shared -fpic calc.c show.c -c libmath.so
PIC (Position Independent Code,位置无关代码) (仅作了解)
调用代码通过相对地址标识调用代码的位置,模块中的指令与该模块被加载到内存中的位置无关
fPIC: 大模式,生成代码比较大,运行速度比较慢,所有平台都支持
fpic:小模式,生成代码比较小,运行速度比较快,仅部分平台支持
总结理解:因为共享文件是大家公用的,所以其内的函数地址都不是写死的,是相对的而不是绝对的,每一个函数都有其偏移量,而 a.out 会生成一个相对地址比如 0x1234,如果想调用函数,那么0x1234+函数偏移量才会指向那个函数的地址
动态库的使用
1编辑库的使用代码
main.c
2编译并链接动态库
直接链接动态库
gcc main.c libmath.so
用 -l 指定库名,用 -L指定库路径
gcc main.c -lmath -L
用 -l 指定库名,用LIBRARY_PATH环境变量指定库路径
export LIBRARY_PATH=$LIBRARY_PATH:.
gcc main.c -lmath
3运行时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径
用以告知链接库在运行时链接动态库
export LD_LIBRARY_PATH=$LIBRARY_PATH:.
4.在可执行程序的链接阶段,并不将所调用函数的二进制代码复制到可执行程序中,而只是将该函数在共享库中的地址嵌入到调用模块中,因此运行时需要依赖共享库
代码:
前边的 main.c calc.c show.c 和他们的 .h 文件以及math.h不变
1.生成目标文件,但是注意,要加fpic
2.打包成静态库
gcc -shared calc.o show.o -o libmath.so
3.编译并链接动态库
其实这里本应报错,其错误是
这里是因为,虽然动态库链接完成,但是根据其理论,在生成后,是每一个函数,在其后都会有一个指令,这个指令让他去找库,而这一步发生了错误,找不到库,那么这里回顾前边理论,找到链接器
运行时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径
用以告知链接器在运行时链接动态库
没错,LD_LIBRARY_PATH就是来管理这个链接的
于是 可以 export LD_LIBRARY_PATH=$LIBRARY_PATH:.
也可以去 .bashrc 中直接添加,如下图第三条
于是这里我们学习了三个环境变量
PATH shell 找可执行程序
LIBRARY_PATH gcc 找库
LD_LIBRARY_PATH load (连接器) 找库
静态库和动态库的区别
静态库的优缺点:
优点:
执行速度快
可执行程序不依赖库的存在
缺点:
文件体积相对较大
更新困难,维护成本高
动态库的优缺点
优点
可执行文件体积小,节省空间
易于链接,便于更新维护
缺点
文件执行速度相对较慢
可执行程序依赖库文件的存在