动静态库
动静态库的概念
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统**从磁盘上的该动态库中复制到内存中,**这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
- 系统默认库命名规则:lib+name.so.version。去掉lib和第一个.后的就是库的名字
动态链接的特点
ldd 文件名
/lib64/libc.so.6 是软链接
当顺序执行到printf 中程序中没有该函数,跳转到库中执行完再返回后续代码执行。动态库并没有把代码拷贝进来,因此程序体积比较小。
objdump -d test:
静态链接的特点
gcc -o $@ $^ -static
静态库是将对应的printf 函数部分拷贝到自己的程序中。
objdump -d test-s
- 静态链接的特点
- 将对应代码拷贝进bin,体积比较大
- bin可移植性强
- 链接的时候纳入进来,可能比较占资源(硬盘和内存)
- 比如每个程序都用了
printf ,那每份程序里面的printf 其实就是冗余的
查看文件的动静态链接属性
file 文件名
动态链接的机理
通过代码区找到映射区的区域,通过页表找到内存中动态库的位置。
当前是一个进程,如果是100个动态链接的进程,代码是只读的,其他进程通过各自页表映射进程地址空间的共享区,因此库我文件只要一份就行。
而静态链接是把拷贝进来的代码放在代码区了,所以代码区变得很大。
生成&&使用静态库
我们要提供两个东西,头文件和库文件。
一批头文件:有什么方法可以使用,接口参数是什么意思
一个、多个库文件:具体的实现,供我们动静态链接。
链接的本质:链接.o文件,lib文件。
而动静态库就是把所有.o文件打包,命令成.a。
#ifndef MY_LIB_H
#define MY_LIB_H
#include<stdio.h>
void myprintf();
#endif
#include"mylib.h"
void myprintf()
{
printf("hello world!\n");
}
gcc -c xxx.c
单纯不带包给别人用只要把.o和.h给别人即可。不过这样使用注意需要makefile的时候加上其.o为依赖项目,不然找不到对应的符号。
test:test.c mylib.o
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf *.o test test-static
#include"mylib.h"
int main()
{
myprintf();
}
但是数量多起来就不方便了,需要打包。
- 自己定义库——一批头文件和源文件,只把源文件处理成.o,使用ar -rc(replace and create)打包成一个静态库
- 给别人使用的时候提供头文件和静态库。但是gcc的时候面临问题,比如头文件和静态库不在当前目录下(如果在当前目录下-I就不需要了),需要
-I 指出头文件目录,需要-L 指明库的搜索路径,-l 指出库搜索路径中的要用的库名(去掉lib和.后的,可以带空格也可以不带),-static 指出静态链接。
ar -rc libxxx.a xxx.o xxx.o
ar 命令是gnu的归档工具,常用于将目标文件打包为静态库
-r (replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。-c (create):建立静态库文件。
path = $(shell pwd) #可以不要这个path使用相对路径
test:test.c
gcc -o $@ $^ -I $(path)/mylib/include -L $(path)/mylib/lib -l mymath -static
.PHONY:clean
clean:
rm -f test
-I 选项指定头文件搜索路径-L 选项指定库文件搜索路径-l 选项指明需要链接库文件路径下的哪一个库
但是这样挺麻烦的,怎么样不用这些选项呢?
有一种粗暴的方式是把自己的头文件和库扔到stdio.h 和libc.a 的系统默认的搜索路径下。但是这样做会污染其他文件环境。所以还是乖乖地放当前路径下文件夹中把选项带上。
(所谓框架就是这样,下载来之后就是一堆头文件和库)
sudo cp mylib/include/* /usr/include/
sudo cp mylib/lib/libmymath.a /lib64/
需要注意的是,虽然已经将头文件和库文件拷贝到系统路径下,但当我们使用gcc编译main.c生成可执行程序时,还是需要-l 指明需要链接库文件路径下的哪一个库。
./test -lmymath
为什么之前使用gcc编译的时候没有指明过库名字?
因为我们使用gcc编译的是C语言,而gcc就是用来编译C程序的,所以gcc编译的时候默认就找的是C库,但此时我们要链接的是哪一个库编译器是不知道的,因此我们还是需要使用-l 选项,指明需要链接库文件路径下的哪一个库。
生成&&使用动态库
extern"C"的实例见c++ 基础的笔记。
windows中的动态库为.dll,静态库是.lib。
gcc -fPIC -c add.c
gcc -fPIC -c sub.c
此时用源文件生成目标文件时需要携带-fPIC 选项:
-fPIC (position independent code):产生位置无关码
说明:
-fPIC 作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。- 如果不加
-fPIC 选项,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的拷贝,并且每个拷贝都不一样,取决于这个.so文件代码段和数据段内存映射的位置。 - 不加
-fPIC 编译出来的.so是要在加载时根据加载到的位置再次重定位的,因为它里面的代码BBS位置无关代码。如果该.so文件被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。 - 我们总是用
-fPIC 来生成.so,但从来不用-fPIC 来生成.a。但是.so一样可以不用-fPIC 选项进行编译,只是这样的.so必须要在加载到用户程序的地址空间时重定向所有表目。
- 第二步:使用-shared选项将所有目标文件打包为动态库
gcc -shared -o libmymath.so add.o sub.o
-shared 代表共享库
将add.h和sub.h放到include下,将动态库文件放到lib下,两个文件都放到mylib下。此时就可以给别人使用了。
生成动态库的makefile :
libmymath.so:myadd.o mysub.o
gcc -shared -o $@ $^
myadd.o:myadd.c
gcc -fPIC -c $<
mysub.o:mysub.c
gcc -fPIC -c $<
.PHONY:output
output: #打包发给别人的
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.so mylib/lib
.PHONY:clean
clean:
rm -rf output *.o libmymath.so
生成.out的makefile :
path = $(shell pwd) #可以不要这个path使用相对路径
test:test.c
gcc -o $@ $^ -I $(path)/mylib/include -L $(path)/mylib/lib -l mymath
.PHONY:clean
clean:
rm -f test
光是这样生成并不能使用动态库,因为程序要使用的时候操作系统不知道动态库在哪。上述的行为是让gcc知道的-l动态库。
因此让操作系统知道使用的动态库方法有三:
-
我们需要将自己的动态库文件拷贝到系统共享库路径下/usr/lib sudo cp mlib/lib/libmymath.so /lib64
-
更改LD_LIBRARY_PATH 类似PATH 帮我们找可执行程序,这个环境变量帮我们找动态库
ldd test
export LD_LIBRARY_PATH=/home/ycb/demo1/mylibso/mylib/lib
ldd test
此时结果正常运行
- 配置/etc/ld.so.conf.d/+ldconfig更新
我们可以通过配置/etc/ld.so.conf.d/ 的方式解决该问题,/etc/ld.so.conf.d/ 路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/ 路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。
使用外部库
系统中其实有很多库,它们通常由一组互相关联的用来完成某项常见工作的函数构成。
gcc -Wall test.c -o test -lm
#include <math.h>
#include <stdio.h>
int main(void)
{
double x = pow(4.0, 5.0);
printf("%lf\n", x);
return 0;
}
对于这份代码,加不加math.h 都能跑出来。当然加了C语言的第三方库的libm.so 也能跑出来。
ls /usr/lib64/libm.so
但是在老版本中可能会找不到对应的库。(比如上次做yacc的编译原理lab的时候没有-l m导致pow一直没反应。
|