Linux 基础 (II)
一、动态库与静态库的对比
- 静态库在文件中静态展开,所以有多少文件就展开多少次,非常吃内存,100M展开100次,就是10G,但是这样的好处就是静态加载的速度快
- 使用动态库会将动态库加载到内存,10个文件也只需要加载一次,然后这些文件用到库的时候临时去加载,速度慢一些,但是很省内存
二、静态库制作及使用
静态库名字以lib开头,以.a结尾,如:libmylib.a
静态库制作及使用步骤:
1. 将 .c 生成 .o 文件
gcc -c add.c -o add.o
2. 使用 ar 工具制作静态库
ar rcs lib库名.a add.o sub.o div.o
3. 编译静态库到可执行文件中:
gcc test.c lib库名.a -o a.out
1.分别构建add.c、sub.c、div1.c如下
//add.c
int add(int a,int b)
{
return a+b;
}
//sub.c
int sub(int a,int b)
{
return a-b;
}
//div1.c
int div1(int a,int b)
{
return a/b;
}
将.c文件生成.o文件,分别执行以下代码:
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c div1.c -o div1.o
2. 使用 ar 工具制作静态库
ar rcs libmymath.a add.o sub.o div.o
查看静态库文件属性:file libmymath.a
3. 编译静态库到可执行文件中
新建test.c文件如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char *argv[])
{
int a=14,b = 7;
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;
}
直接对test.c进行编译(gcc -c test.c -o test)则出错(链接时),原因:未加入静态库导致未声明。
解决:使用静态库进行编译
gcc -c test.c libmymath.a -o test
再在test.c中加入乘法函数,以及在main函数中打印:
int mul(int a,int b)
{
return a*b;
}
通过命令:gcc -c test.c libmymath.a -o test进行编译,再运行,也能得出乘法结果
但是在编译时加入参数:gcc -c test.c libmymath.a -o test -Wall,则出现警告信息
通过编译器隐式声明来解决,即在test.c中加入隐式声明。编译器只能隐式声明返回值为int的函数形式:int add(int ,int );
如果函数不是返回的int,则隐式声明失效,会报错。在test.c中则加入如下隐式声明:
int add(int,int);
int sub(int,int);
int div1(int,int);
重新进行编译:gcc -c test.c libmymath.a -o test -Wall,警告信息便不存在
以上方法需知道库里的函数,再加到代码里,较为麻烦。
下面使用头文件的方法来加载静态库:
新建mymath.h如下:
#ifndef _MYMATH_H_
#define _MYMATH_H_
int add(int ,int);
int sub(int ,int);
int div1(int ,int);
#endif
以上代码为头文件守卫,防止头文件重复包含
test.c改为:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include "mymath.h"
int mul(int a,int b)
{
return a*b;
}
int main(int argc, char *argv[])
{
int a=14,b = 7;
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));
printf("%d x %d = %d\n",a,b,mul(a,b));
return 0;
}
再进行编译:gcc -c test.c libmymath.a -o test -Wall,同样得出结果,此方法更加科学
再者,将mymath.h放入inc目录中,libmymath.a放入lib目录下,通过以下命令进行编译:
gcc test.c ./lib/libmymath.a -o test -I ./inc
三、动态库制作及使用
动态库制作及使用步骤:
1. 生成位置无关的.o文件
gcc -c add.c -o add.o -fPIC
使用这个参数过后,生成的函数就和位置无关,挂上@plt标识,等待动态绑定
2. 使用 gcc -shared 制作动态库
gcc -shared -o lib库名.so add.o sub.o div.o
3. 编译可执行程序时指定所使用的动态库. -l:指定库名 -L:指定库路径
gcc test.c -o a.out -l mymath -L ./lib
4. 运行可执行程序./a.out
1.对add.c、sub.c、div1.c生成位置无关的.o文件,分别执行以下代码:
gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
gcc -c div1.c -o div1.o -fPIC
2.制作动态库 gcc -shared -o lib库名.so add.o sub.o div.o
gcc -shared -o libmymath.so add.o sub.o div.o
3.编译程序
动态库在lib目录下,头文件在inc目录下,代码如下:
gcc test.c -o a.out -l mymath -L ./lib -I ./inc
4.执行文件./a.out,出错
四、动态库加载错误原因及解决方式
error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory
出错原因分析:
链接器: 工作于链接阶段,完成数据段合并和地址回填,工作时需要 -l 和 -L
动态链接器: 加载动态库,工作于程序运行阶段,工作时需要提供动态库所在目录位置(通过环境变量指定动态库所在位置:export LD_LIBRARY_PATH=动态库路径
)
执行如下程序:
export LD_LIBRARY_PATH = ./lib
再执行./a.out则成功(法一)(临时生效,终端重启环境变量失效)
当关闭终端,再次执行a.out时,又报错。
这是因为,环境变量是进程的概念,关闭终端之后再打开,是两个进程,环境变量发生了变化。
要想永久生效(法二),需要修改bash的配置文件:.bashrc 建议使用绝对路径
(1)vi ~./bashrc
(2)写入export LD_LIBRARY_PATH = ./lib 保存
(3)..bashrc或者source.bashrc或者重启终端让修改后的.bashrc生效
(4)./a.out成功
配置文件法(法三):
1)sudo vi /etc/ld.so.conf
2) 写入 动态库绝对路径 保存 在库的路径下执行pwd,再复制
3)sudo ldconfig -v 使配置文件生效。
4)./a.out 成功!!!--- 使用 ldd a.out 查看
五、扩展了解:数据段合并和地址回填
1.在链接阶段,来自不同目标文件的数据段会被合并到一起,形成最终可执行文件中的统一的数据段。数据段:用于存储初始化的全局变量和静态变量的内存区域
2.在编译阶段,编译器通常不知道变量或函数最终在内存中的地址,因此会生成一些带有占位符的代码。链接器在解析和合并不同目标文件中的符号时,会确定这些符号的最终地址