文章目录
- 前置知识:
- 库的命名
- C标准库
- 动静态库
- 安装C/C++静态库
- 完整的库需要的东西
- 制作静态库
- 制作
- 使用
- 一个小疑惑:
- 制作动态库
- 制作
- 使用
- 总结:
前置知识:
一般库分为两种:动态库和静态库 静态库和动态库本质就是文件!也有inode
库的命名
库文件的命名一般为: libXXXXX.so
或者libXXXXX.a-
…
如何知道库的真实名字?
去掉lib前缀 去掉.a-
.so-
的后缀,剩下的就是库的名称
例如: libc-2.17.so动态库的真实名字就是:c-2.17
C标准库
问:C标准库在哪里?
安装在系统中
验证:
ldd 可执行程序
:显示可执行程序依赖的库
其中这个libc-2.17.so
就是C语言的标准库,名字为:c-2.17
那C++的库呢?
库名字为: stdc++
gcc默认是动态链接编译:
如果我们希望静态链接编译,就需要加-static
选项
动静态库
在Linux中:如果是动态库:库文件是以.so为后缀的
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
如果是静态库:库文件是以.a为后缀的
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库
在windows中,动态库以.lib为后缀, 静态库以.dll为后缀
动态链接和静态链接的大小:
静态库 程序在编译链接的时候把库的代码复制到可执行文件当中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大
动态库生存的可执行程序体积比较小, 因为动态链接只是把我们要关联的函数在库里面关联起来,不会进行任何的拷贝的行为, 当函数执行时,如果要执行库函数的代码,就直接跳转到库中执行,
总结:
静态库
优点:
- 使用静态库生成可执行程序后,该可执行程序就可以独自运行,一旦形成就不再依赖库了,哪怕依赖的库被删除了,也不影响该可执行程序的运行
缺点:
- 使用静态库生成可执行程序会占用大量空间,特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库,这时在内存当中就会存在大量的重复代码
动态库
在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接.动态库在多个程序间共享,节省了磁盘空间,操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间
优点:
- 节省磁盘空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码
缺点:
- 必须依赖动态库,可执行程序的移植性就比较差,如果库缺失了,可执行程序就运行不了
1)一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
2)在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接
3)动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间,操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间
安装C/C++静态库
sudo yum install glibc-static
sudo yum install libstdc++-static
完整的库需要的东西
一套完整的库,包括3样东西: 库文件本身, 头文件, 说明文档
我们以前写C/C++代码的时候,一般都是将头文件和源文件分离, 在.h中声明. .c或者.cpp中定义,为什么要这样设计呢?
因为我们要制作库! 这样方便使用,而且保证了隐私性, 其中头文件会暴露库中方法的使用,而且方便维护!
—我们要知道程序的编译要经历如下过程:
- 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成
xxx.i
文件 - 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成
xxx.s
文件 - 汇编: 将汇编指令转换成二进制指令,最终形成
xxx.o
文件 - 链接: 将生成的各个
xxx.o
文件进行链接,最终形成可执行程序
如果我们想把自己的方法给别人使用, 我们可以给别人提供源代码和头文件, 如果我们不想把源文件给别人, 我们可以把所有的.o文件打包成库,发给别人
制作库的本质:将所有的.o文件以某种方式打包
制作静态库
准备工作:
test_lib目录下的文件:
other目录的文件:
此时直接编译发生错误, 报错原因是 gcc不能找到库的源文件, 因此我们需要在编译的时候跟上源文件的位置:
gcc mytest.c ../test_lib/add.c ../test_lib/sub.c -o mytest
上述还不能称为制作库!
制作
我们现在test_lib目录下写这样一个Makefile文件:
lib_math.a:add.o sub.o
ar -rc lib_math.a $^ #将所有的.o文件打包归档打包形成lib_math
%.o:%.c #生成.o文件依赖.c
gcc -c $< #依次根据.c文件编译生成所有的同名.o文件, $<:把上面的内容展开之后,一个一个的编译
.PHONY:output #发布库
output:
mkdir output
cp *.h output #把当前路径下的所有.h文件拷贝到output文件夹下
cp lib_math.a output
.PHONY:clean
clean:
rm -rf *.o lib_math.a output #删除所有的.o文件和lib_math和output
Makefile文件的含义:
1.将库文件编译为.o
- 其中**%是通配符, **%.o作含义是:把当前目录下的所有.o结尾的文件展开
2.用ar命令, 将所有的.o文件进行打包
ar
命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar
命令的-r
选项和-c
选项进行打包-r
(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件-c
(create):建立静态库文件
ar -rc libname.a #要打包的.o文件
3.用make output进行发布库,把相关内容放到output文件夹中
- 注意:要把.h也发布出去. 这样别人才能知道库里面有什么
查看静态库的内容:
ar -tv lib_math.a
其中:
- -t 选项: 列出静态库的目录列表
- -v 选项:列出详细信息
我们甚至可以把我们写的库安装到系统中,但是非常不建议!!!
.PHONY:install
install:
cp *.h /usr/include #把当前路径下的所有.h文件拷贝到/usr/include下
cp lib_math.a /lib64 #把静态库拷贝到lib64文件夹下
使用
假设要使用的人所在目录是 friend
直接编译会报错!因为找不到头文件,为什么会找不到呢?
- 因为编译器不会查找当前源文件同级目录下的目录中的内容,因为我们此时的头文件在output目录下, **所以我们需要带上
-I./output
**选项,告诉编译器在当前目录的output目录下查找头文件
但是仍然报错了, 因为找不到库函数的实现了,所以我们还需要告诉库的路径在哪里: -L./output
实际可能存在很多库,编译器不知道链接这个路径下的哪个库,所以我们还需要带上库的名称 -l_math
- 注意:我们这里的库是: lib_math.a 但是去掉头和尾才是库的名字 _math
总结:
1.因为编译器不知道你所包含的头文件add.h和sub.h在哪里所以需要指定头文件的搜索路径
2.因为头文件add.h当中只有my_add函数的声明,并没有该函数的定义,所以还需要指定所要链接库文件的搜索路径
3.实际中,在库文件的lib目录下可能会有大量的库文件,因此我们需要指明需要链接库文件路径下的哪一个库
-I
,-L
,-l
这三个选项后面可以加空格,也可以不加空格
-
-I :指定头文件的搜索路径
-
-L:指定库搜索路径
-
-l :指明要链接哪一个库
此时我们可以搞一个Makefile方便编译:
test:test.c
gcc -o $@ $^ -I./output -L./output -l_math
.PHONY:clean
clean:
rm -rf test
一个小疑惑:
我们之前写C/C++代码的时候,也使用到了各自库,为什么我们不需要加上述的这些选项来指明些什么呢?
因为之前的库默认在系统的路径下: 编译器能识别这些存在于配置文件中的路径,因为我们使用gcc编译的是C语言,而gcc就是用来编译C程序的,所以gcc编译的时候默认就找的是C库,但此时我们要链接的是哪一个库编译器是不知道的,因此我们还是需要使用-l
选项,指明需要链接库文件路径下的哪一个库
例如: 库文件: /lib64 ,/usr/lib 头文件:/usr/include
所以说,如果我们想投机取巧,完全可以把对应的库和头文件拷贝到默认路径下,但是 这种做法严重不推荐! 可能会"污染"里面的东西 或者我们写的把库里面的东西覆盖掉了(命名不标准)
制作动态库
制作
1.此时我们还是需要将库文件全部编译为.o文件, 但是此时需要添加一个选项: -fPIC
, 因为要形成与位置无关码position independent code
-fPIC
作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行.这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的
gcc -fPIC -c add.c sub.c
2.把库进行打包, 此时使用的不是ar
指令,而是-shared
指令
gcc -shared -o $% $^#形成一个动态链接的共享库
3.使用make shared_lib 进行发布我们的动态库
- 注意:要把.h(头文件)和so(动态库)也发布
所以Makefile文件为:
#形成一个动态链接的共享库
lib_math.so:add.o sub.o
gcc -shared -o $@ $^
%.o:%.c #生成.o文件依赖.c文件
# $<:依次编译.c文件生成同名的.o文件 -fPIC选项:生成位置无关码,程序内部的地址方案与位置无关
gcc -fPIC -c $<
.PHONY:shared_lib
shared_lib:
mkdir shared_lib
cp *.h shared_lib
cp lib_math.so shared_lib
.PHONY clean:
clean:
rm -rf *.o lib_math.so
使用
我们先把shared_lib文件夹拷贝到friends目录下
同样的,这里我们也需要指定路径搜索头文件 :-I./shared_lib
,同样的,也需要指明库文件的搜索路径:-L./shared_lib
,也需要指明链接的是哪一个库:-l_math
库lib_math.so的真实名字是:_math, 所以我们可以编写我们的Makefile了:
test:test.c
gcc -o $@ $^ -I./shared_lib -L./shared_lib -l_math
.PHONY:clean
clean:
rm -f mytest
此时可以编译通过,但是运行的时候就报错了!
为什么呢?静态库不是也是这样做的吗? 为什么在这里就不行了呢?
因为静态库把目标模块导入直接拷贝到我们的文件中, 运行时不需要再去找了, 而动态库,编译的时候仍然需要去找库在哪里, 运行的时候,也需要加载动态库
但是我们上面编译的时候不是已经告诉了库路径了吗? 但这只是告知了编译器头文件的库路径在哪里,当程序编译完成后,已经和编译器无关了, 运行的时候加载器还是不知道库路径在哪里
所以我们要怎么解决呢?
做法1:拷贝.so文件到系统共享库路径下(不推荐)
动态库的头文件直接拷贝到共享库路径: /usr/bin
目录下!
做法2:更改LD_LIBRARY_PATH
通过导入LD_LIBRARY_PAYH
这个环境变量 这个环境变量是程序运行动态查找库时所要搜索的路径, 指明程序启动后动态库的搜索路径!
export LD_LIBRARY_PATH=路径 #导入LD_LIBRARY_PATH环境变量
注意:这种在命令行上设置的环境变量,只在当前登录期间有效,
做法3:配置/etc/ld.so.conf.d/
这个是系统搜索动态库的路径, /etc/ld.so.conf.d/
路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/
路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库 这种做法可以永久生效!
我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了
1)将库文件所在目录的路径存入一个以.conf为后缀的文件当中
2)该.conf文件拷贝到/etc/ld.so.conf.d/
目录下
3)需要使用ldconfig
命令将配置文件更新一下,更新之后系统就可以找到该可执行程序所依赖的动态库了
总结:
1.制作库的本质: 将所有的.o文件打包
- 静态库:
ar -rc
- 动态库:
gcc -fPIC -shared
2.发布库:将 所有的.h文件 *.h 和 .a /.so发布出去
3.使用:拿到别人的库和头文件,加入到自己的项目中
静态库可以直接使用,动态库还要告知系统动态库的路径, 如果你提供的是一个静态库,那我们只能将这个库静态链接到程序中;,如果你提供的是一个动态库,那我们只能将这个库动态链接到程序中
如果既想动态链接,又想静态链接,一般要提供两个版本的库文件
问:gcc默认是怎么链接
gcc优先是动态链接 并且是release版本!