【Linux】基础I/O——库,动静态库

     我们在使用c语言的时候经常使用下面这个语句

 

它实际上是包含stdio.h库的意思,那么库是什么?

这就是我们今天要讲的东西

1.库

1.1.什么是库

注:我下面提供了多种解释,力求将库的概念讲清楚

        简单的来说,库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常

        本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

        库是一些可重定向的二进制文件,这些文件在链接时可以与其他的可重定向的二进制文件一起链接形成可执行程序。

        我们在编写一个程序的时候,经常会遇到好多重复或常用的部分,如果每次都重新编写固然是可以的,不过那样会大大降低工作效率,并且影响代码的可读性,更不利于后期的代码维护。我们可以把他们制作成相应的功能函数,使用时直接调用就会很方便,还可以进行后期的功能升级。

        库通俗的说就是把这些常用函数的目标文件打包在一起,即库的本质为一堆.o文件的集合。不包含main但是包含了大量的方法,提供相应函数的接口,便于程序员使用。库是别人写好的现有的,成熟的,可以复用的代码,我们只需要知道其接口如何定义,便可以自如使用。比如我们常使用的printf函数,就是 C 标准库提供的函数。我们在使用时只需要包含相应的头文件就可以使用,在连接阶段会连接到具体printf的方法上去,而不用关心printf函数具体是如何实现的,这样就大大提高了程序员编写代码的效率。

回顾一下,将一个程序编译成可执行程序的步骤:

        其中链接将二进制文件(.o文件)链接成一个可执行的命令,主要是把分散的数据和代码收集并合成一个单一的可加载并可执行的的文件。

关于以上各类文件后缀的含义:

  • .o文件是目标文件,它是源代码编译后生成的二进制文件。.o是object的缩写,表示这个文件包含了目标代码,即机器语言代码。
  • .a文件是静态库文件,它是由多个目标文件打包而成的。.a是archive的缩写,表示这个文件是一个归档文件,包含了多个目标文件。
  • .so文件是动态库文件,它也是由多个目标文件组成的。.so是shared object的缩写,表示这个文件是一个共享对象,可以在程序运行时被载入。
  • .i文件是预处理后的C或C++源代码文件。预处理器会处理源代码中的宏定义、条件编译和头文件包含等指令,生成一个.i文件,其中包含了预处理后的源代码。
  • .s文件是汇编语言源代码文件。它包含了用汇编语言编写的程序代码,可以通过汇编器转换为机器语言代码。
  • .out文件是可执行文件,它包含了可以直接在计算机上运行的机器语言代码。.out是output的缩写,表示这个文件是编译器输出的结果。
  • .exe文件是Windows操作系统下的可执行文件。它包含了可以直接在Windows操作系统上运行的机器语言代码。.exe是executable的缩写,表示这个文件是可执行的。与.out文件类似,.exe文件也是编译器输出的结果,它可以直接在计算机上运行。不过,.exe文件只能在Windows操作系统上运行,而不能在其他操作系统(如Linux或macOS)上运行。

 库中有main函数吗?下面会告诉你答案:

        当有多个不同的源文件中的main函数调用这些功能函数时,每次都要重新对这几个函数重复预处理、编译、汇编操作,各自生成.o文件,然后再和调用功能函数的源文件(一般是main函数)生成的.o,最后才生成可执行程序。

        这样会有很多重复的操作,所以一般将这些常用的函数所在的.cpp文件预处理、编译、汇编生成的多个.o文件打包在一起,称之为库。而事实上我们经常使用的<stdio.h>、<iostream>、使用各种STL容器包含的头文件都是这么做的。

        由此可以见,库的本质是若干个目标文件(.o文件)的集合。每个.o文件都包含了由源码编译生成的二进制代码,以供调用。

        严格地说,库并不是可执行程序的半成品。库是一组预先编译好的目标文件(.o文件)的集合,它们可以被链接到可执行程序中,以提供某些功能。库中的目标文件包含了机器语言代码,但它们并不能直接运行。要生成一个可执行程序,需要将库中的目标文件与其他目标文件(如含有main函数的目标文件)链接在一起,然后由链接器生成一个可执行文件。

  1. 编译型语言需要通过编译器将源代码转换为机器语言,然后才能在计算机上运行。编译器会将源代码翻译成计算机能够直接执行的机器语言,这样程序就可以直接运行,而不需要解释器。
  2. 编译型语言的优点是运行速度快,因为它们直接运行机器语言,而不需要经过解释器的解释。但是,编译型语言的缺点是开发过程中需要花费更多的时间进行编译。

 1.2.头文件与库的关系

        头文件提供方法说明,库提供方法的实现,头和库是有对应关系的,是要组合在一起使用的
头文件是在预处理阶段就引入的,程序在链接时链接的本质其实就是链接库!
        有了上面的一点基础知识以后我们就能够去见一见库了,Linux系统在安装时已经为我们预装了C&C++的头文件和库文件。

对于C/C++头文件在Linux里面一般在/usr/include目录下面存放我们的头文件

对于C/C++的库文件,一般在/usr/lib64里面。

到这里我们也能够解释一些常见的操作背后的逻辑了:

问:1. 我们在使用像vs2022这样的编译器时,要下载并安装开发环境,这其中是在下载什么?

        答:安装编译器软件,安装要开发的语言配套的库和头文件。

问:2. 我们在使用编译器,都会有代码补全,但是都需要先包含头文件,这时为什么呢?

        答:代码补全是编辑器根据的将用户输入的内容,不断的在被包含的头文件中进行搜索匹配,所以代码补全,功能是依赖头文件而来的!

问:3. 我们在写代码的时候,我们的编辑器怎么知道我们的代码中有语法错误?

        答:编译器很复杂,编译器有命令行的模式,还有其他自动化的模式,编辑器或集成开发环境可以在后台不断的调用编译器检查语法问题,从而达到语法检查的效果。

1.3. Linux中库的标准命名

  1. 静态库的名字一般为libxxx.a,其中xxx是该lib的名称。
  2. 动态库的名字一般为libxxx.so.major.minor,xxx是该lib的名称,major是主版本号, minor是副版本号。

2.动静态库

我们回到上面

         其中链接将二进制文件链接成一个可执行的命令,主要是把分散的数据和代码收集并合成一个单一的可加载并可执行的的文件。发生在代码静态编译、程序被加载时的连接称为静态连接,对应连接的库叫做静态库;发生在程序执行时的连接称为动态连接,对应连接的库称为动态库。

另外对于C/C++来说其库的名称也是有规范要求的,例如在Linux下:一般要求是lib + 库的真实名称 +(版本号)+ .so /.a + (版本号),版本号是可以省略不写的。

  • 静态库(.a):程序在编译链接的时候把库的代码链接拷贝到可执行文件中。程序运行的时候将不再需要静态库。
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序可以共享使用库的代码。

2.1.认识动静态库

下面以我们最初学习的最简单的代码,初步认识动静态库

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

         编译生成可执行程序mylib。这个程序能够成功调用库函数printf,归功于gcc编译器在生成可执行程序时,将C标准库也链接进可执行程序中。

        通过指令ldd filename查看可执行程序依赖的库文件:

[xy@xy 3_14]$ ldd mylib
        linux-vdso.so.1 =>  (0x00007ffea9bfb000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fc6231d9000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fc6235a7000)

ldd 是一个命令,它用于打印程序或库文件所依赖的共享库列表。它不是一个可执行程序,而只是一个 shell 脚本。

         其中,libc.so.6就是这个可执行程序依赖的库文件,通过ll指令查看这个该路径下这个库文件的属性:

        表明它其实是软链接到同目录下的libc-2.17.so文件,通过file指令,查看该文件的文件类型:

        如果一个库文件是 shared object,那么它是一种特殊的目标文件,可以在程序运行时被加载(链接)进来。在 Linux 下,动态链接库(shared object file,共享对象文件)的文件后缀为 .so。使用动态链接库的优点是:程序的可执行文件更小,便于程序的模块化以及更新,同时,有效内存的使用效率更高。

也就是说:这个libc-2.17.so是一个动态库。

 动静态库的后缀因平台而不同:

  • Linux :.so:动态库;.a:静态库。
  • Windows :.dll:动态库;.lib:静态库。

去掉前缀和后缀,剩下的就是库的名字。在这里,libc.so.6实际上是C语言的动态库,库名是c

libc.so.6 是 glibc 的软链接。glibc 是 GNU 发布的 libc 库,即 C 运行库。

         默认情况下,gcc/g++ 采用动态链接的方式链接第三方库。

        例如,当你指定 -lpng 时,链接程序就会去找 libpng.so。不过,gcc/g++ 提供了一个 -static 参数,可以改变默认链接方式。如:

gcc -static mylib.c -o mylib-s

        其中mylib-s是静态链接版本生成的可执行程序,可见,动态链接生成的可执行程序的大小比静态链接的小不少。

        使用ldd指令试着查看它是否有依赖的其他库文件:

[xy@xy 3_14]$ ldd mylib-s
        not a dynamic executable

         说明静态链接生成的可执行程序不依赖其他库文件,同样地,用file指令查看它的文件类型:

可能出现的错误:

/usr/bin/ld: cannot find -lc collect2: error: ld returned 1 exit status

这个错误信息 /usr/bin/ld: cannot find -lc 表示链接器找不到 C 库,可以通过以下步骤解决(centos):

安装静态 C 库:

sudo yum update
sudo yum install glibc-static

刷新数据库:

updatedb

查看是否已经在系统上存在 libc.a 文件:

locate libc.a

如果打印:/usr/lib64/libc.a则说明安装成功,可继续执行gcc指令。

2.2. 动、静态库的优缺点

  • 动态库

优点

  • 节省内存空间:运行时除了代码加载到内存,其所使用的动态库也会被加到内存,如果其他进程也需要使用该动态库,则只要调整其他进程的映射关系到已经在内存加载好的库即可,不需要重复加载,节省内存空间。
  • 将一些程序升级变得简单,用户只需要更新动态库即可。

缺点:依赖库,如果可执行程序生成后,删除库则无法运行这个可执行程序。

  • 静态库

优点:可执行程序的运行与库无关,因为在编译连接时库已经链接到可执行程序中,删除库后仍可运行。

缺点

  • 多个进程使用同一库会导致内存资源浪费。
  • 静态库对程序的更新、部署和发布页会带来麻烦。如果静态库更新了,所有使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。

5. 几个补充的点

5.1 gcc默认优先链接动态库

如果同时存在动态库和静态库文件,gcc会优先链接动态库

如果你想强制gcc链接静态库,

  1. 可以直接指定静态库的全称,或者使用-static选项。
  2. 你也可以使用-Bdynamic和-Bstatic选项在命令行中切换首选项。

5.2 动态库可分批加载到内存

动态库在程序运行时才被加载,它和可执行文件是分开的,只是可执行文件在运行的某个时期调用了它。动态库可以实现进程之间资源共享,有一份就行。

可执行程序先加载,代码区的代码运行到动态库的方法时,可以先跳转到共享区,而共享区是保存了页表和动态库的映射关系的,所以使用完动态库的方法以后,再返回到代码区继续执行即可。

由此可见,静态库是在可执行程序自己的进程地址空间中跳转的。

5.3 动静态库的区别

静态库和动态库的最大区别是,静态库链接的时候把库直接加载到程序中(也就是直接拷贝一整份库),而动态库链接的时候,它只是保留接口,将动态库与程序代码独立。

这样就可以提高代码的可复用度和降低程序的耦合度。动态库相对于静态库有便于更新拓展、减小体积等诸多优势。

但这不意味着静态库是一无是处的。

当你更希望简单易用,尽量免除后顾之忧,那就首选静态(隐式)连接。

静态库在链接时就和可执行文件在一块了,因此,对于同样的程序,静态链接的要比动态链接加载更快。

        所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的。

5.4 动态库的意义

动态库把对一些库函数的链接载入推迟到程序运行的时期,可以实现进程之间的资源共享,将一些程序升级变得简单。

库是别人写好的现有的、成熟的、可以复用的代码,用户可以按照说明使用库。

现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

可以减少代码冗余,只需要一份即可。

分批加载后,只要建立相对地址关系即可。如果有很多个进程都要用同一个库,只要建立映射关系即可。只要加载一次,就可以在内存中被多份代码复用。

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值