重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
库的概念:一组.o文件的集合,或者说.obj文件(windows下)的集合,也就是链接生成可执行文件时用到的文件
在我的理解里,库就是别人写的代码,比如库函数,第三方库,包括之后会用到的网络库,这些都是别人写的,我们只是拿过来使用而已
那为什么我们要使用别人的代码呢?
因为:为了开发效率和代码的健壮性
那我们要如何使用别人的库?
- 借助库、开源代码、和网络。下面我们主要了解库的使用
- 如果别人要用我们的功能,我们又不想给源码,就可以把源码打包成一个库给对方用
库的种类:
链接分为动态链接和静态链接,库分为动态库和静态库。静态链接是一个过程,这个过程中用到的库就是静态库,动态链接同理
使用三方库时的链接过程:
链接过程简单概括为地址和空间分配,符号解析和代码重定位
符号指的是变量,函数名等,符号解析则是将符号的定义和符号的引用建立联系
重定位我简单理解为可以准确的运行我们的程序,比如一个函数,我们可以精准的找到函数入口的地址并进行运行
符号解析后生成符号表,linux下查看符号表命令:
readelf -s 文件名.o
代码的重定位:我们运行的指令都有其对应的地址,比如我们进入一个函数就是跳到一个表示函数入口的地址,代码变化指令也会跟着变化,地址自然就变化了,比如前面一开始有4条代码,修改后有5条,那对应的指令和地址可能都要发生变化,函数入口就变化了,此时的代码里重新计算地址的过程就叫做重定位。如果有多个模块,成千上万行代码,修改这个地址的工作就是庞大的,早期还是人工改,现在都交给链接器了
静态链接概念:
- 静态链接:是指编译阶段把静态库的代码加入到可执行文件中,或者说把用到的函数的全部链接到可执行文件中(但是链接器是以文件为单位进行操作的,比如要用printf就得链接所有包含printf的文件)。这样生成的可执行文件不需要借助外部的库,生成后可以直接使用,这就是静态链接。缺点是文件比较大,每次更新都得重新编译,优点是运行较快
- 简言之:把需要的代码和数据从库全拷贝到当前代码
动态链接概念:
- 动态链接:是什么时候要用到库里的东西就什么时候去找,存的也只是索引和相关的部分信息,所以占用空间不大。好处是占用空间小,所有的程序可以共用同一个库,更新也比较方便,缺点就是比较慢。如果我们有及时上百个进程都用了同一个库,那相比静态链接就大大节省了空间
- 简言之:动态库可以被多个进程共享,与进程地址空间里的共享区有关
gcc -o 命令默认动态链接,加上-static选项就表示静态链接,可通过file命令查看链接属性
#includeint main()
{int a=1;
printf("%d\n",a);
return 0;
}
ldd命令,查看程序或者库文件所依赖的共享库,即打印动态链接依赖的库列表
下图中:libc.so.6,把lib和so去掉,中间剩下的c就是库名,.so就表示动态库,.a表示静态库,.6是主版本号
对于静态链接与动态链接的总结:
- 静态链接会拷贝代码和数据,再将其链接进可执行文件,因为链接器的操作单元又是文件,所以静态的链接的文件一般都较大,优点是快(不用去库里找东西自然快),不需要依赖外部(库丢失了也能跑)。但是不好更新,每次更新都得重新编译,比如包含了这个库里的某个函数的文件就全得更新
- 动态链接就是啥时候要就啥时候找,链接器看到动态链接的符号也会去找,只不过不分配地址,重定位的操作等到了程序装载时才做。但因为是通过索引去找,所以多个进程可以共享一个库(库丢失就跑不了了),一些场景下可以大大节省空间资源,更新时也方便,只用编译相对应的模块
下面的动静态库生成剖析通过下面四个函数来验证:
add.h
#pragma once
int add(int x,int y);
add.c
#include"add.h"
int add(int x,int y)
{return x+y;
}
sub.h
#pragma once
int sub(int x,int y);
sub.c
#include"sub.h"
int sub(int x,int y)
{return x-y;
}
下面以一个例子说明生成静态库的操作,我们利用ar工具把两个C文件打包成一个静态库
。静态库里放.o文件,可以做到不暴露源码,再建一个目录放头文件,告诉使用者里面有哪些方法可以用
静态库的打包过程:
关于ar命令的补充:
ar
命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar
命令的-r
选项和-c
选项进行打包
-r
(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件-c
(create):建立静态库文件-t
:列出静态库中的文件-v
(verbose):显示详细的信息
打包流程:
- 第一步:让所有源文件生成对应的目标文件,即gcc -c add.c -o add.o等操作
- 第二步:使用ar命令将所有目标文件打包为静态库,即ar -rc …等操作
- 第三步:将头文件和生成的静态库组织起来,即cp add.h sub.h …等操作
- 第四步(可有可无):编写Makefile后,只需一个
make
就能生成所有源文件对应的目标文件进而生成静态库
生成的静态库名为libmymath.a.1,后面的1表示版本号,建议我们自己打包库的时候去掉版本号,不然后面使用的时候会显示找不到库…(也不知道为啥会找不到,网上查阅资料后发现静态库一般都把版本号写在文件里,再通过strings+文件里添加字符串version来标识文件是哪个版本,也有利用命令行参数来打印版本号的)
gcc -o main main.c -I ./include -L ./lib -lmymath -static
//-I指定去哪找头文件,-L指定去哪找库文件,-l+库名表示去找哪个库
//因为默认路径的缘故,gcc编译C代码都不用加-I -L -l等选项
多个进程可以共享一个多态库,如下图:
动态库上面提到是要的时候再去找,所以多个进程用动态库时用的都是同一份代码,合理的节省了资源
-fPIC作用于编译时期,产生与位置无关的代码,编译后的代码用的是相对地址
用相对地址的原因
:共享库加载进内存的位置是不确定的,如果不加-fPIC选项,可能生成带有绝对位置的代码,那链接器链接时可能就需要去重新定位各个符号的位置,那共享库的代码可能就发生了变化,我们的程序此时就要去维护这段发生了变化的代码,维护进行的操作就是拷贝一份,那动态链接的作用就不大了(动态链接本来是要用的时候直接用库里的,现在我程序自身要维护一份更改后的代码,显然是不合理的),所以与位置无关的代码其实就是使用了相对地址的代码,这么做与elf文件的格式有关(借助了elf文件里面的“段”)。可以简单理解为规定了一根线,关于地址的坐标都是相对于这根线的,不管我程序加载到内存的哪个位置,只要通过计算这根线到当前位置的距离就可以获取到(算出)正确的位置。真正清晰的理解需要了解elf文件的格式,编译原理、链接器的相关知识
动态库的打包相对于静态库来说有一点点差别,但大致相同,我们还是利用上面的四个文件进行打包演示:
动态库的打包流程:
第一步:让所有源文件生成对应的目标文件
此时用源文件生成目标文件时需要携带
-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选项将所有目标文件打包为动态库
- 与生成静态库不同的是,生成动态库时我们不必使用ar命令,我们只需使用gcc的-shared选项即可
gcc -shared -o libcal.so add.o sub.o
第三步:将头文件和生成的动态库组织起来
- 与生成静态库时一样,为了方便别人使用,在这里我们可以将
add.h
和sub.h
这两个头文件放到一个名为include的目录下,将生成的动态库文件libcal.so
放到一个名为lib的目录下,然后将这两个目录都放到mlib下,此时就可以将mlib给别人使用了第四步:使用Makefile
- 当然,生成动态库也可以将上述所要执行的命令全部写到Makefile当中,后续当我们要生成动态库以及组织头文件和库文件时就可以一步到位了
- 编写Makefile后,只需一个
make
就能生成所有源文件对应的目标文件进而生成动态库
- 一个
make output
就能将头文件和动态库组织起来
我们使用下面main.c的代码作为例子:
//main.c
#include#include"add.h"
#include"sub.h"
int main()
{int a=add(0,1);
printf("%d\n",a);
return 0;
}
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧