目录
一、动静态库的概念
二、静态库的打包与使用
2.1 静态库的打包
2.2 静态库的使用
三、动态库的打包与使用
3.1 动态库的打包
3.2 动态库的使用
3.3 运行动态库的四种方法
四、总makefile
一、动静态库的概念
静态库: Linux下,以.a为后缀的文件。程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。本质是在编译时把静态库中的代码(不是一次性加载,而是分页加载)复制到了进程的的代码区中。
动态库: Linux下,以.so为后缀的文件。程序在运行的时候才去链接动态库的代码,在可执行程序装载或运行时,由操作系统的装载程序加载库,多个程序共享使用库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
静态链接: 将库中的相关代码复制进可执行程序中的过程。
动态链接: 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中。
链接的本质: 使.o文件可以找到要调用的函数的位置
库文件名称: 比如libc.so,去掉前缀lib和后缀.so,剩下的就是库名。libhello.a的库名就是hello。
头文件gcc的默认搜索路径是 /usr/include
库文件的默认搜索路径是 /usr/lib64把文件拷贝到系统的默认路径下就叫做库的安装,拷贝之后就不用 -I 和 -L了
实例演示: 分别使用静态链接和动态链接编译生成两个可执行程序,比较两个程序的大小
使用gcc静态链接编译时,命令要带上**-static** 选项,如下:
gcc -o test test.c -static
可以看到的是,使用静态库静态链接成的可执行程序比动态链接生成的可执行程序要大很多。
我们还可以通过file命令查看文件的链接属性:
还可以通过ldd 命令查看可执行程序的依赖库,动态链接生成的可执行程序才有依赖库,静态链接升序的可执行程序不依赖任何库文件,因为库文件的代码已经复制进可执行程序了。
因为这里是动态链接,不仅要让编译器动态库的路径,还要让操作系统知道,所以这里我们需要导入一个环境变量LD_LIBRARY_PATH,如下:
export LD_LIBRARY_PATH=/home/dgz/linux/lesson26/lib/uselib/output/lib
执行完之后
总结动静态库的优缺点
静态库
- 优点: 程序运行的时候将不再需要静态库,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
- 缺点:一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
动态库
- 优点: 动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间.动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标
- 缺点: 程序运行的时候依赖动态库, 据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。
二、静态库的打包与使用
2.1 静态库的打包
静态库打包: 本质其实就是将代码编译成.o的二进制文件,然后进行打包。
为了更好地演示这个过程,我创建了mymath.c、mymath.h、myprint.c和myprint.h四个文件,内容分别如下:
mymath.c
#include "mymath.h"
int Add_(int a,int b)
{
return a+b;
}
mymath.h
#pragma once
#include <stdio.h>
extern int Add_(int a,int b);
myprint.c
#include "myprint.h"
void printf_(const char* str)
{
printf("hello %s [%d]\n",str,(int)time(NULL));
}
myprint.h
#pragma once
#include <stdio.h>
#include <time.h>
extern void printf_(const char* str);
如下:
打包静态库的步骤
- 先将myadd.c和mysub.c 变成生成对应的二进制文件
-
使用ar 归档工具对两个二进制文件进行打包,同时带上选项**-rc**(r和c分别代表replace和creat),这里的库名是hello。
ar -rc libhello.a *.o
-
上面这两个步骤其实就把静态库打包好了,下面我们还有做一个工作就是发布静态库,简单地说,就是把头文件和静态库组织起来,头文件放在include 下,如下:
-
这样一个库文件就可以给别人使用了。
上面的所有步骤我们可以写进Makefile里,利用make指令一键打包和make output发布,如下:libhello.a:myprint.o mymath.o ar -rc libhello.a myprint.o mymath.o mymath.o:mymath.c gcc -c mymath.c -o mymath.o myprint.o:myprint.c gcc -c myprint.c -o myprint.o .PHONY:output output: mkdir -p output/lib mkdir -p output/include cp -rf *.h output/include cp -rf *.a output/lib .PHONY:clean clean: rm -rf *.o *.a output
2.2 静态库的使用
先把静态库放到一个测试目录下:
然后编写一段代码:
#include "myprint.h"
#include "mymath.h"
int main()
{
int ret = Add_(1,2);
printf("%d\n",ret);
printf_("dgz");
return 0;
}
编写Makefile:
使用gcc编译时,采用静态链接编译,所以要带上选项**-static**,此外,因为我们使用了别人给的静态库,所以我们还有告诉编译器库文件所在路径,头文件所在路径以及库名,所以要用到以下三个选项:
- -L: 指明库文件所在路径
- -I: 指明头文件所在路径
- -l: 指明库文件名称,这里库名就是hello(去掉前缀lib和后缀.a)
这里我们可以使用绝对路径,使用下面的shell命令获取当前所在路径:
path=$(shell pwd)
Makefile编写后如下:
path=$(shell pwd)
mytest:test.c
#-l 指定库目录名称 -L 库目录路径 -I 指定头文件路径
gcc -o $@ $^ -I $(path)/output/include -L $(path)/output/lib -l hello -static
.PHONY:clean
clean:
rm -f mytest
使用file指令查看链接属性:
三、动态库的打包与使用
3.1 动态库的打包
我们同样还是使用上面的那四个文件进行演示。
步骤:
- 先将mymath.c和myprint.c 变成生成对应的二进制文件。注意这里生成的二进制文件要带上选项**-fPIC**,产生路径无关码,也就是这里使用相对地址,是动态确定的,不存在绝对地址
gcc -c -fPIC mymath.c -o mymath_d.o gcc -c -fPIC myprint.c -o myprint_d.o
- 使用gcc带上选项**-shared** (生成共享的库格式)对二进制文件进行打包
gcc -shared myprint_d.o mymath_d.o -o libhello.so
- 最后一步就是对动态库进行发布,也就是将库文件和头文件进行组织打包
- 编写Makefile:
libhello.so:myprint_d.o mymath_d.o gcc -shared myprint_d.o mymath_d.o -o libhello.so mymath_d.o:mymath.c gcc -c -fPIC mymath.c -o mymath_d.o myprint_d.o:myprint.c gcc -c -fPIC myprint.c -o myprint_d.o .PHONY:output output: mkdir -p output/lib mkdir -p output/include cp -rf *.h output/include cp -rf *.so output/lib .PHONY:clean clean: rm -rf *.o *.so output
3.2 动态库的使用
先把动态库放到测试目录下:
然后编写Makefile,和静态库的使用类似:
path=$(shell pwd)
mytest_d:test.c
#-l 指定库目录名称 -L 库目录路径 -I 指定头文件路径
gcc -o $@ $^ -I $(path)/output/include -L $(path)/output/lib -l hello
.PHONY:clean
clean:
rm -f mytest_d
编译程序: 如果此时直接对程序进行编译,不会报错,但是执行的时候会报错,无法打开共享库里面的文件
因为这里是动态链接,不仅要让编译器动态库的路径,还要让操作系统知道,所以这里我们需要导入一个环境变量LD_LIBRARY_PATH,如下(上面已经说过,这里重复一次)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dgz/linux/lesson26/lib/uselib/output/lib
此时再执行程序就不会报错了:
使用file指令查看程序的链接属性:
使用ldd指令查看程序依赖的库:
3.3 运行动态库的四种方法
- 将对应到.so和.h文件拷贝到/usr/lib64和/usr/include
- 系统在在搜索头文件时会先在系统默认路径下搜索(上面两个路径),如果没找到,但是环境变量LD_LIBRARY_PATH设置了,也会在该环境变量下搜索,这种方法是内存级的,退出就没有了。
- 修改配置文件,配置/etc/ld.so.conf.d/
路径下原有的文件
我们想要配置它只需要在该路径下创建一个.conf文件
然后将我们要配置的路径写进该文件
最后执行一下ldconfig
- 建立软连接
建立成功,然后就可以找到了。
四、总makefile
.PHONY:all
all:libhello.so libhello.a
libhello.so:myprint_d.o mymath_d.o
gcc -shared myprint_d.o mymath_d.o -o libhello.so
mymath_d.o:mymath.c
gcc -c -fPIC mymath.c -o mymath_d.o
myprint_d.o:myprint.c
gcc -c -fPIC myprint.c -o myprint_d.o
libhello.a:myprint.o mymath.o
ar -rc libhello.a myprint.o mymath.o
mymath.o:mymath.c
gcc -c mymath.c -o mymath.o
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o
# 发布
.PHONY:output
output:
mkdir -p output/lib
mkdir -p output/include
cp -rf *.h output/include
cp -rf *.a output/lib
cp -rf *.so output/lib
.PHONY:clean
clean:
rm -rf *.o *.a *.so output