Linux动静态库

目录

一、了解动静态库

1.1 静态库的特点

1.2 动态库的特点 

二、静态库的打包与使用

2.1 静态库的打包

2.2 静态库的使用

三、动态库的打包与使用

3.1 动态库的打包

3.2 动态库的使用


一、了解动静态库

一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤:

  1. 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件
  2. 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件
  3. 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件
  4. 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序

实际上,所有库本质是一些目标文件(xxx.o)的集合,库的文件当中并不包含主函数而只是包含了大量的方法以供调用,可以认为动静态库本质是可执行程序的"半成品"

  • 在Linux当中,以.so为后缀的是动态库,以.a为后缀的是静态库
  • 在Windows当中,以.dll为后缀的是动态库,以.lib为后缀的是静态库

下面通过一份简单的代码来具体认识一下动静态库

#include <stdio.h>
int main()                                                                                                                                                                       
{
    printf("hello lib\n");//printf()为库函数
    return 0;
}

 通过ldd命令可以查看可执行程序所依赖的库文件

从上图可以看出test可执行程序依赖 /lib64/libc.so.6 ,但是其是一个软链接,链接的源文件为/lib64/libc-2.17.so。这个 libc-2.17.so 实际上就是C动态库,去掉一个库前缀lib,再去掉后缀,剩下的就是库的名字。

而gcc/g++默认进行的是动态链接,想使用静态链接需添加 -static 选项,且静态链接生成的可执行程序并不依赖其他库文件。

而且 使用静态库的可执行程序的大小 明显大于 使用动态库的可执行程序的大小

1.1 静态库的特点

静态库是程序在编译链接的时候把库的代码复制到可执行文件当中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大

  • 优点:使用静态库生成可执行程序后,该可执行程序可独自运行,不再依赖库了
  • 缺点:使用静态库生成可执行程序会占用大量空间,特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库,这时在内存当中就会存在大量的重复代码

1.2 动态库的特点 

动态库是程序在运行的时候才去链接相应的动态库代码的,不必将库的代码复制到可执行文件当中,使得可执行程序的大小较使用静态库而言更小,节省磁盘空间。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。

在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。操作系统采用虚拟内存机制使得物理内存中的一份动态库被所有要使用该库的进程共用,节省了内存空间。

  • 优点:节省内存和磁盘空间,当多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码
  • 缺点:必须依赖动态库,否则无法运行

二、静态库的打包与使用

下面演示时,都以这些文件为例。源文件main.c、test1.c和test2.c,头文件test1.h和test2.h

main.c文件

#include "test1.h"
#include "test2.h"
int main()
{
    test1();
    test2();                                                                                                                                                                     
    return 0;
}

test1.c文件

#include "test1.h"                                                                                                                                                               
void test1(){
    printf("This is test1!!!\n");
}

test1.h文件

#pragma once
#include <stdio.h>
void test1();            

test2.c文件

#include "test2.h"
void test2(){
    printf("This is test2!!!\n");                                                                                                                                                
}

test2.h文件

#pragma once                                                                                                                                                                     
#include <stdio.h>
void test2();

2.1 静态库的打包

第一步:将要打包的源文件生成对应的目标文件

test1.o:test1.c
    gcc -c tets1.c -o test1.o
test2.o:test2.c
    gcc -c test2.c -o test2.o

第二步:使用ar命令将所有目标文件打包为静态库

ar命令是gnu的归档工具。下面使用ar命令的-r-c选项进行打包

  • -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件
  • (create):建立静态库文件
libtest.a:test1.o test2.o
    ar -rc libtest.a test1.o test2.o

此外,我们可以用ar命令的- t 和 -v 选项查看静态库当中的文件。

  • -t:列出静态库中的文件
  • -v(verbose):显示详细的信息

第三步:将头文件和生成的静态库组织

mkdir -p my_lib/include
mkdir -p my_lib/lib
cp ./*.h ./my_lib/include 
cp ./*.a ./my_lib/lib

当想发布库给别人使用时,发布这个文件夹即可

makefile完整版

libtest.a:test1.o test2.o
    ar -rc $@ $^
test1.o:test1.c
    gcc -c $^ -o $@
test2.o:test2.c
    gcc -c $^ -o $@

.PHONY:output
output:
    mkdir -p my_lib/include
    mkdir -p my_lib/lib
    cp ./*.h ./my_lib/include 
    cp ./*.a ./my_lib/lib

.PHONY:clean
clean:
    rm -rf ./my_lib ./*.o ./*.a           

2.2 静态库的使用

方案一:使用选项

  • -I:指定头文件搜索路径
  • -L:指定库文件搜索路径
  • -l:指明需要链接库文件路径下的具体哪一个库

方案二:将头文件和库文件拷贝到系统路径下

[bjy@VM-8-2-centos usedir]$ sudo cp output/include/* /usr/include/
[bjy@VM-8-2-centos usedir]$ sudo cp output/lib/libtest.a /lib64/

注意:虽已将头文件和库文件拷贝到系统路径下,但使用gcc生成可执行程序时,还需具体指明链接哪一个库

实际上拷贝头文件和库文件到系统路径下的过程,就是安装库的过程。但并不推荐将个人编写的头文件和库文件拷贝到系统路径下,会对系统文件造成污染。

三、动态库的打包与使用

3.1 动态库的打包

第一步:将要打包的源文件生成对应的目标文件

此时需要添加 -fPIC 选项 ,即产生位置无关码

test1.o:test1.c
    gcc -fPIC -c test1.c -o test1.o
test2.o:test2.c
    gcc -fPIC -c test2.c -o test2.o

-fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行(通过页表与共享区建立映射关系)。所以共享库被加载时,在内存的位置不是固定的 。

第二步:使用-shared选项将所有目标文件打包为动态库

libtest.so:test1.o test2.o
    gcc -shared $^ -o $@

第三步:组织头文件与生成的动态库

mkdir -p my_lib/include
mkdir -p my_lib/lib
cp ./*.h ./my_lib/include 
cp ./*.so ./my_lib/lib        

makefile完整版

libtest.so:test1.o test2.o
    gcc -shared $^ -o $@
test1.o:test1.c
    gcc -fPIC -c test1.c -o test1.o
test2.o:test2.c
    gcc -fPIC -c test2.c -o test2.o

.PHONY:output
output:
    mkdir -p my_lib/include
    mkdir -p my_lib/lib
    cp ./*.h ./my_lib/include 
    cp ./*.so ./my_lib/lib                                                                                                                                                       

.PHONY:clean
clean:
    rm -rf *.so *.o my_lib

3.2 动态库的使用

接下来使用与静态库相同的方式,用-I选项指定头文件搜索路径,用-L选项指定库文件搜索路径,最后用-l选项指明库的名称。但是可以发现可执行程序貌似找不到这个动态库的位置。

因为上面的三个选项是传给gcc编译器的,但可执行程序生成后就与编译器无关了,所以当程序运行起来后操作系统并找不到所依赖的动态库。

解决方案一:拷贝.so文件到系统共享库路径下

[bjy@VM-8-2-centos uselib]$ sudo cp ./my_lib/lib/libtest.so /lib64

解决方案二:更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径,只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可

但是这个方案只能临时解决问题,当将终端关闭再重新开启后,导入的环境变量就会丢失。

解决方案三:配置/ect/ld.so.conf.d/

/etc/ld.so.conf.d/路径下存放的都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,然后就在每个路径下查找用户所需要的库。若将第三方库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能找到第三方库文件。

先将库文件所在目录的路径存入一个以.conf为后缀的文件当中,再将该文件拷贝到/etc/ld.so.conf.d/目录下,最后使用 ldconfig 命令将配置更新一下即可。

猜你喜欢

转载自blog.csdn.net/GG_Bruse/article/details/128810497