• Linux打包和使用动静态库


    🌴库

    在我的理解里,库就是别人写的代码,比如库函数,第三方库,包括之后会用到的网络库,这些都是别人写的,我们只是拿过来使用而已。

    • 那为什么我们要使用别人的代码呢?

    为了开发效率和代码的健壮性。

    张三手写一个STL给李四用,李四敢用吗🤡,肯定是不敢用的,需要STL时肯定是使用库里的。

    我们使用的printf也是借助了C语言提供的库。

    • 那我们要如何使用别人的功能?

    借助库、开源代码、和网络。下面我们主要了解库的使用。

    如果别人要用我们的功能,我们又不想给源码,就可以把源码打包成一个库给对方用。

    • 库是什么?

    一组.o文件的集合,或者说.obj文件(windows下)的集合,也就是链接生成可执行文件时用到的文件

    image-20220712112135473

    这篇博客的gcc部分探究了生成可执行程序的各个过程👉Linux中gcc、g++、gdb以及make/Makefile的使用_

    • 库的种类

    链接分为动态链接和静态链接,库分为动态库和静态库。静态链接是一个过程,这个过程中用到的库就是静态库,动态链接同理。

    • 使用库的时候

    image-20220712162344645

    🌴链接简述

    清晰的理解链接需要编译原理的知识,如果想要深入探究其各个细节,可参考**《程序员的自我修养》**这本书,书上详细介绍了目标文件,以及可执行程序是怎么来的,eof文件格式与进程地址空间的联系。(太底层了,我当前对其各个步骤也不过是一知半解,自然是无法阐述清楚的

    🌵链接过程简述

    链接过程简单概括为地址和空间分配,符号解析和代码重定位。

    符号指的是变量,函数名等,符号解析将符号的定义和符号的引用建立联系。

    重定位我简单理解为可以准确的运行我们的程序,比如一个函数,我们可以精准的找到函数入口的地址并进行运行。

    • 符号解析后生成符号表,linux下查看符号表

    image-20220712161217145

    • 代码的重定位,我们运行的指令都有其对应的地址,比如我们进入一个函数就是跳到一个表示函数入口的地址,代码变化指令也会跟着变化,地址自然就变化了,比如foo前面一开始有4条代码,修改后有5条,那对应的指令和地址可能都要发生变化,函数入口就变化了,此时的代码里重新计算地址的过程就叫做重定位。如果有多个模块,成千上万行代码,修改这个地址的工作就是庞大的,早期还是人工改,现在都交给链接器了。

    🌵静态链接

    静态链接是指编译阶段把静态库的代码加入到可执行文件中,或者说把用到的函数的全部链接到可执行文件中(但是链接器是以文件为单位进行操作的,比如要用printf就得链接所有包含printf的文件)。这样生成的可执行文件不需要借助外部的库,生成后可以直接使用,这就是静态链接。缺点是文件比较大,每次更新都得重新编译,优点是运行较快。

    把需要的代码和数据全拷贝到本模块。

    🌵动态链接

    动态链接是什么时候要用到库里的东西就什么时候去找,存的也只是索引和相关的部分信息,所以占用空间不大。好处是占用空间小,所有的程序可以共用同一个库,更新也比较方便,缺点就是比较慢。如果我们有及时上百个进程都用了同一个库,那相比静态链接就大大节省了空间。

    动态库可以被多个进程共享,与进程地址空间里的共享区有关。

    🌵动静态链接的例子

    gcc -o 命令默认动态链接,加上-static选项就表示静态链接,可通过file命令查看链接属性。

    #include 
    
    int main()
    {
      int a=1;
      printf("%d\n",a);
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    下图的高清图片

    image-20220712234218268

    ldd命令,查看程序或者库文件所依赖的共享库,即打印动态链接依赖的库列表。

    image-20220712234823304

    libc.so.6,把lib和so去掉,中间剩下的c就是库名,.so就表示动态库,.a表示静态库,.6是主版本号。

    🌵小结

    静态链接会拷贝代码和数据,再将其链接进可执行文件,因为链接器的操作单元又是文件,所以静态的链接的文件一般都较大,优点是快(不用去库里找东西自然快),不需要依赖外部(库丢失了也能跑)。但是不好更新,每次更新都得重新编译,比如包含了这个库里的某个函数的文件就全得更新。

    动态链接就是啥时候要就啥时候找,链接器看到动态链接的符号也会去找,只不过不分配地址,重定位的操作等到了程序装载时才做。但因为是通过索引去找,所以多个进程可以共享一个库(库丢失就跑不了了),一些场景下可以大大节省空间资源,更新时也方便,只用编译相对应的模块。

    🌴生成动静态库

    下面的例子借助这四个文件来生成动态库和静态库

    add.h

    #pragma once 
    
    int add(int x,int y);
    
    • 1
    • 2
    • 3

    add.c

    #include"add.h"
    
    int add(int x,int y)
    {
      return x+y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    sub.h

    #pragma once 
    
    int sub(int x,int y);
    
    • 1
    • 2
    • 3

    sub.c

    #include"sub.h"
    int sub(int x,int y)
    {
      return x-y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🌵生成静态库

    下面以一个例子说明生成静态库的操作,我们利用ar工具把两个C文件打包成一个静态库。静态库里放.o文件,可以做到不暴露源码,再建一个目录放头文件,告诉使用者里面有哪些方法可以用。

    image-20220713100632292

    具体操作

    image-20220713095002593

    生成的静态库名为libmymath.a.1,后面的1表示版本号,建议我们自己打包库的时候去掉版本号,不然后面使用的时候会显示找不到库…(也不知道为啥会找不到,网上查阅资料后发现静态库一般都把版本号写在文件里,再通过strings+文件里添加字符串version来标识文件是哪个版本,也有利用命令行参数来打印版本号的)

    随笔记录:vim分屏命令是vs,ctrl+w+w可以切换窗口。

    🌵使用静态库

    gcc -o main main.c  -I ./include -L ./lib -lmymath -static
    
    • 1

    -I指定去哪找头文件,-L指定去哪找库文件,-l+库名表示去找哪个库

    image-20220713105509236

    关于库的搜索路径👉链接静态库文件时的搜索路径

    因为默认路径的缘故,gcc编译C代码都不用加-I(大写的i) -L -l等选项。

    🌵生成动态库

    多个进程可以共享一个多态库。

    image-20220714001320318

    动态库上面提到是要的时候再去找,所以多个进程用动态库时用的都是同一份代码,合理的节省了资源。

    进程数很多时节省的资源是可观的,静态链接用不到共享区

    具体操作

    image-20220714005210661

    -fPIC作用于编译时期,产生与位置无关的代码,编译后的代码用的是相对地址。

    用相对地址的原因:共享库加载进内存的位置是不确定的,如果不加-fPIC选项,可能生成带有绝对位置的代码,那链接器链接时可能就需要去重新定位各个符号的位置,那共享库的代码可能就发生了变化,我们的程序此时就要去维护这段发生了变化的代码,维护进行的操作就是拷贝一份,那动态链接的作用就不大了(动态链接本来是要用的时候直接用库里的,现在我程序自身要维护一份更改后的代码,显然是不合理的),所以与位置无关的代码其实就是使用了相对地址的代码,这么做与elf文件的格式有关(借助了elf文件里面的“段”)。可以简单理解为规定了一根线,关于地址的坐标都是相对于这根线的,不管我程序加载到内存的哪个位置,只要通过计算这根线到当前位置的距离就可以获取到(算出)正确的位置。真正清晰的理解需要了解elf文件的格式,编译原理、链接器的相关知识。

    c++ - GCC -fPIC option - Stack Overflow

    查看动态库下有哪些函数:readelf -s和nm -D命令

    • image-20220714005618976

    • image-20220714005739355

    🍈Makefile

    将上面的操作写到makefile

    libmymath.so:add.o sub.o
    	gcc -shared -o $@ $^
    
    add.o:add.c
    	gcc -c $^ -o $@ -fPIC
    sub.o:sub.c
    	gcc -c $^ -o $@ -fPIC 
    
    .PHONY:display
    display:
    	nm -D libmymath.so #查看生成的动态库里的函数方法
    
    .PHONY:clean
    clean:
    	rm -f libmymath.so add.o sub.o
    
    .PHONY:package
    package:
    	mkdir -p library ;\#反斜杠是为了一个进程内跑多个命令,不然的话多行命令会在不同的Shell下运行
    	mkdir -p library/lib;\
    	mkdir -p library/include;\
    	cp *.h  library/include;\
    	cp libmymath.so library/lib;\
    	tree library
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20220714093319501

    使用Makefile构建文件

    image-20220714093611456

    .PHONY是内置目标名

    🌵使用动态库

    记我们写的程序为main.c

    #include 
    #include"add.h"
    #include"sub.h"
    int main()
    {
      int a=add(0,1);
      printf("%d\n",a);
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20220714095720691

    在环境变量下加入依赖的动态库的路径

    image-20220714100816317

    ldd命令用于打印程序或者库文件所依赖的共享库列表

    此外还有把我们库的路径加入到系统的默认路径下,或者像下面这种自己写一个conf文件加入到etc目录下的ld.so.conf.d里面。但是这两种都不推荐,因为加入的同时污染系统的配置,不过一些情况下图方便也可以选择这么做。

    image-20220714101236273

  • 相关阅读:
    万和电气进入“双引擎”时代
    java基础之内部类[31]
    记录UNIAPP打包苹果iOS·APP
    在Ubuntu 16.04上安装和配置VNC
    Jquery
    WF100DPZ 1BG S6 DT数字压力传感器
    亚马逊云EC2助力5G产品测试
    【MySQL】Spring Boot项目基于Sharding-JDBC和MySQL主从复制实现读写分离(8千字详细教程)
    DPDK系列之三十一DPDK的并行机制简介
    干洗店洗鞋店软件模版,成品系统功能非常完善;
  • 原文地址:https://blog.csdn.net/m0_53005929/article/details/125779074