【c/c++】静态库
1、静态库介绍静态库是指在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中的这种库。程序编译一般需经预处理、编译、汇编和链接几个步骤。静态库特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。静态库和动态库是两种共享程序代码的方式,它们的区别是:静态库在程序的链接阶段被复
1、静态库动态库介绍
静态库是指在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中的这种库。
程序编译一般需经预处理、编译、汇编和链接几个步骤。静态库特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。
静态库和动态库是两种共享程序代码的方式,它们的区别是:静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。使用动态库的优点是系统只需载入一次动态库,不同的程序可以得到内存中相同的动态库的副本,因此节省了很多内存。
静态库在文件中静态展开,所以有多少文件就展开多少次,非常吃内存,但是这样的好处就是静态加载的速度快
使用动态库会将动态库加载到内存,10个文件也只需要加载一次,然后这些文件用到库的时候临时去加载,速度慢一些,但是很省内存
动态库和静态库各有优劣,根据实际情况合理选用即可。
2、静态库制作
静态库名字以lib开头,以.a结尾
例如:libmylib.a
静态库生成指令
ar rcs 库名字 原材料
ar rcs libmylib.a file1.o
步骤一
写好源代码
三个文件:add.c、sub.c、div1.c(加法、减法、除法)
int add (int a, int b) {
return a + b;
}
int sub (int a, int b) {
return a - b;
}
int div1 (int a, int b) {
return a / b;
}
test.c文件
/*************************************************************************
> File Name: test.c
> Author: Winter
> Created Time: 2021年09月06日 星期一 20时21分35秒
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[])
{
int a = 10, b = 5;
printf("%d + %d = %d \n", a, b, add(a, b));
printf("%d - %d = %d \n", a, b, sub(a, b));
printf("%d / %d = %d \n", a, b, div1(a, b));
return 0;
}
步骤二
编译源代码生成.o文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c div1.c -o div1.o
步骤三
制作静态库
ar rcs libMyMath.a add.o sub.o div1.o
静态库的使用:
gcc test.c libMyMath.a -o test
上图中,test.c直接使用了mymath库中的add,sub,div1函数,所以在编译时要导入静态库
编译时出现了函数未定义的警告,可以忽略,让系统生成默认的定义。
出现的警告,用编译器隐式声明来解决的
编译器只能隐式声明返回值为int的函数形式:int add(int ,int );
如果函数不是返回的int,则隐式声明失效,会报错。
在test.c中加入函数声明:
再次进行编译,就不会报错了:
上面这个方法,需要库的使用者知道库里的函数,完事儿一个一个加到代码里,不太行。
一般静态库也会有相应的头文件。
下面使用头文件的方法来加载静态库
#ifndef _MYMATH_H_
#define _MYMATH_H_
int add (int,int);
int sub(int,int);
int div1(int,int);
#endif
ifndef、define为头文件守卫,防止在代码中多次include头文件,多次展开静态库,带来的额外开销,这样也不会报错了,而且更加科学。
再在test.c中加入头文件。
下面将静态库和头文件分别放至其他目录下
新建resource、inc、lib目录。
将*.c(test.c除外。图片有误)、*.o放到resource目录下;将*.h(头文件)放到inc;将*.a(静态库)放到lib目录下
运行过程如下
最终目录如下
接下来重新编译
gcc test.c ./lib/libMyMath.a -o test -I ./inc/
-I: 指定头文件所在目录位置。
3、动态库制作
写在源代码里的函数,相对main函数偏移是一定的,链接时,回填main函数地址之后,其他源代码里的函数也就得到了地址。
动态库里的函数会用一个@plt来标识,当动态库加载到内存时,再用加载进去的地址将@plt替换掉。
查看反汇编
objdump -dS test > out
vi out
printf是动态库函数,依赖@plt。回填的是函数指针。
【】
制作动态库的步骤
步骤一:生成位置无关的.o文件
*.c文件的内容和静态库里面的内容一样。
步骤二:制作动态库
gcc -shared -o lib库名.so add.o sub.o div.o
库名:libMyMath.so。【lib是固定的,so是固定的,中间的MyMath是自己定义的】
将生成的动态库libMyMath.so放到新创建的lib路径下。
将MyMath.h头文件放到inc目录下。
步骤三:编译程序
文件分布如下:动态库在lib目录下,头文件在inc目录下
下面编译文件
gcc test.c -o test -l MyMath -L ./lib/ -I ./inc/
MyMath是动态库的名称(去掉lib,去掉.so);
-l: 指定库名
-L 指定动态库路径
-I 指定头文件
步骤四:执行文件,出错
步骤五
出错原因分析:
连接器: 工作于链接阶段,a.out生成前使用,工作时需要 -l 和 -L
动态链接器: 工作于程序运行阶段,工作时需要提供动态库所在目录位置【位置固定】
法一:通过环境变量
指定动态库路径并使其生效,然后再执行文件
通过环境变量指定动态库所在位置:export LD_LIBRARY_PATH=动态库路径
当关闭终端,再次执行a.out时,又报错。
这是因为,环境变量是进程的概念,关闭终端之后再打开,是两个进程,环境变量发生了变化。
法二:永久生效
写入 终端配置文件。 修改.bashrc 【建议使用绝对路径】
1) vi ~/.bashrc
2) 写入 export LD_LIBRARY_PATH=动态库路径 保存
3). .bashrc/ source .bashrc / 重启 终端 ---> 让修改后的.bashrc生效
4)./a.out 成功!!!
查看动态库的位置
法三:拷贝自定义动态库 到 /lib (标准C库所在目录位置)
放到 /lib或者/lib/x86_64-linux-gnu下
再把~/.bashrc的配置注释掉
查看动态库位置
法四: 配置文件法
1)sudo vi /etc/ld.so.conf
2)写入 动态库绝对路径 保存
3)sudo ldconfig -v 使配置文件生效。
4)./a.out 成功!!!--- 使用 ldd a.out 查看
更多推荐
所有评论(0)