学习系统编程No.15【深入动静态库】

简介: 学习系统编程No.15【深入动静态库】

引言:

北京时间:2023/4/5/11:04,天空阴沉,非常符合今天清明节的身份,不知道是大部分学校都放假一天,还是就我们学校,反正清明节回不了家,昨天上了一个中国优秀传统文化的课,老师给我们讲了李白和杜甫的婉转一生,发现,原来在古代文人墨客的社会地位并不是很高呀!不知是自命清高,还是望眼欲穿凡尘,自以世外谪仙之感,放浪于形骸之外,凭天生我材必有用,游历世间豪万丈,早年千古绝句伴左右,豪气云霄酒中欢,挥金如土何莫愁,何莫愁!晚年诗篇更上一层楼,悲茅草屋前几多愁,叹牢狱之中愁上愁,愁上愁!所以在古代,做官才是王道,科举才是正道呀!就算是李白、杜甫这样的文人墨客,绝世诗人,还是抵不上科考落榜,政治报复不能实现之痛呀!同理,在清明节的今天,杜牧的一首《清明》,也是无尽思索!清明时节雨纷纷,路上行人欲断魂。借问酒家何处有?牧童遥指杏花村。好一个何处有!好一个杏花村!好一个高堂明镜悲白发,好一个百年多病独登台!总:举杯消愁愁更愁,古往诗人同销万古愁!撤……,今天让我们接着学习上篇博客动静态库的知识以及进程间通信的有关知识


image.png


深入动静态库

上篇文章,我们已经浅浅的了解了一下Linux系统下的动静态库,发现在Linux系统下,库文件如百花争放一样,多的我头痛,最重要的是这么多库文件,除了C库,我居然一个不认识,充分意识到自己对知识了解的孤陋,看来还是太年轻了,所以小伙子不可以松懈哦!所以接下来我们就具体的来认识一下什么是库吧!


简简单单,从问题和现象出发:

问题:我们用过库吗?(还是那句话,库没用过,但是裤子我穿过,哈哈哈!)

现象:VS编译器中的库在哪里?


平时我们想要在VS上使用C语言或者C++写代码的时候,我们可以发现,第一步一定是在文件中包含头文件,#include<stdio.h>、#include< iostream > 并且可以发现,如果我们不包含特定的头文件,有的接口我们就无法使用,所以可以得出结论,包含头文件就是为了可以为我们提供我们需要使用的接口,所以头文件和库究竟有什么关系呢?


1.首先,头文件是指在C/C++编程中,可以写在程序里面,以#include 的形式包含的文件,如上述代码

2.其次,库文件是将一系列的源文件进行编译打包,形成的二进制文件包,其中封装着函数接口,在编程中可以由其它源文件调用,并且分为动态库和静态库两种


根据上述得出结论: 头文件是可读的文本文件,库文件是一些不可读的二进制文件,并且头文件主要是函数接口定义的地方,普通的函数接口(自己实现的)具体实现是在我们自己的.c或者.cpp文件中,如果是库函数,那么具体的实现此时就是在对应的库文件中,所以我们可以发现,对应的头文件和对应的库文件在本质上是有联系,对应头文件中的函数接口定义和对应库文件中函数接口的实现本质上是一样的,只不过一个是声明,一个是实现,所以可以发现,头文件和对应的库文件是存在一个千丝万缕的关系,如下:

浅显的理解就是:头文件是库文件的说明文件,头文件包含的是该头文件具有的功能和这些功能的使用方法,但这些功能的实现和具体的使用步骤却都是在库文件中生成,所以总的来说,就是你想要使用某一个接口函数,你有两个选择,一是你自己实现,二是你去调用库里面的实现,但是在调用库里面的实现的时候,你又不需要直接去库里面调用,你直接包含头文件就行,因为此时对应的头文件中已经有该函数接口的声明,有了声明,就等于你可以直接使用,只要等到了最后生成可执行的时候,编译器根据对应的你包含的头文件,将对应头文件的库文件加载到内存,此时编译器就可以根据头文件展开之后的函数接口声明去调用已经被加载到内存之中的库文件中的实现了,这样就实现了一个库文件中接口的调用,完成了你想要完成的功能!


注意:库文件本质上就是一系列源文件编译打包之后的二进制文件,所以当程序被运行加载到内存时,对应的库文件直接加载到内存使用即可(也就是注意头文件的展开就等于是将很多的函数接口进行声明,然后调用的使用,直接去调用被加载到内存里面的库文件的实现就行了)


重点强调:头文件中的所有函数声明和参数列表,必须和库文件中的实现保持一致性,并且虽然没有规定库函数调用必须要包含头文件,但是最好使用头文件的方式去调用库文件中的接口实现


所以如上述所说,我们不仅穿过裤子,我们还使用过库,真好!哈哈哈!


所以VS编译器中,我们平时使用的头文件和库文件在哪里呢? 如下:

我们当初刚开始下载VS编译器,并且搭建相关的开发环境时,当时安装的不仅是编译器软件,还要安装开发语言配套的库文件和头文件,如下图所示:


44.png


所以如上图,当我们选择了相应的桌面应用和移动应用时,代表的就是我们想要安装环境的具体的库文件和头文件,例如上述,我们选择的是C/C++,所以此时编译器就默认会将C/C++的库文件和头文件帮我们安装到磁盘对应的VS编译器路径当中,供给我们在使用VS编译器的时候,编译器可以自动根据代码中的头文件去调用相应磁盘上的库文件,实现程序的成功运行。


语法提醒的本质

并且要明白,语法提醒的本质:编译器会自动的将用户输入的内容,不断的在被包含的头文件中进行搜索,所以自动提醒功能是需要依赖于头文件的,并且本质上就是字符串的前缀匹配而已,所以我们在写代码的时候,我们的环境变量是可以知道我们的代码中有哪些地方有语法问题的,具体原因如下:


因为编译器有命令行模式,还有其它自动化的模式,可以帮助我们不断进行语法检查,本质上是因为,编译器在我们进行写代码的时候,会不断的调用语法检查的功能,也就是会不断的将我们写的代码翻译成二进制,然后将这些二进制代码去编译,最终将编译的结果返回给我们,如果编译不通过,此时就将有语法错误的位置用波浪线进行标记,并且显示到我们的显示器上,最终起到语法报错的功能,所以可以发现,每当我们写了一个语法错误,这个错误不可能瞬间就返回给我们,而是要过一会,我们才可以看到波浪线标志,所以这个就是因为编译器内部此时正在进行编译和计算(操作系统进行一些列操作),运用之前学过的进程和被打开文件和文件描述符等知识可以具体解释


所以此时头文件和语法提醒问题在现象层面,我们就可以理解了,并且知道搭环境的本质就是在安装各种我们开发需要的库文件和头文件


为什么要有库

本质:提高开发效率(C++中的STL库就是典型的例子)但是要明白,在学习的过程中,我们不能知识肤浅以为会使用库里面的一些接口就是学会了如何使用库,以为自己拥有了使用库来提高开发效率的能力,因为单单只是认识库里的接口,然后使用库,这样的学习方式是不好的,不能将知识融会在一起,所以最好的学习方式,就是自己造轮子(自己去了解库中的接口是如何实现的),只有这样,我们才可以熟练的使用轮子,从而真正做到提高开发效率!


自己封装库的使用方式

类似于我们之前博客中有关文件操作库的实现,本质上就是在调用系统调用接口的同时,自己封装实现一些我们以后开发可以用到的接口,类似C++库和C库,里面有非常多我们在编写代码时可能会用到的函数接口实现,例如:sort、cin、cout、printf、scanf等!当然最关键的还是C++中的STL库,STL是属于语言库中的功能库,各种功能十分强大


并且要注意,不同的系统里面的库是不同的,使用的库也是不同的,所以为什么有的软件在安卓系统上可以使用,但是在i苹果系统(ios)上却不能使用,原因就是因为系统不同,库的实现不同,使用的库不同


如下图:就是Linux系统上自带的库文件


image.png


所以如上图,可以看出,在一个系统中,库是非常多的,并且发现,一个库文件的名称,就是libxxx.so或者libxxx.a,所以将库文件开头的lib去掉,此时我们就得到了这个库的名称,并且 注意云服务器上一般只会存在动态库,而不会存在静态库,因为静态库需要单独安装


动态库和静态库的区别


1.静态库

这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大–空间,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。


2.动态库

这类库的名字一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用–时间,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级/更新比较方便


总结:静态库需要将整个库整合到目标代码,而动态库可以通过条件编译指令的方式,通过头文件的展开,将函数接口声明,然后在目标代码中执行到特定的函数接口时,直接去调用库中的相应函数接口即可(因为有头文件的展开声明)


库文件的具体使用方式

所以在自己使用库的过程之中,其实本质上,就是将别的函数接口包装在一个文件之中,然后为了避免该源码文件的泄露,将该文件生成一个二进制文件,然后当别的文件需要使用这个接口的时候,将这个接口的二进制文件复制到该目标文件所在的路径中,然后将要执行的文件也进行运行,然后将需要使用到的库文件和目标文件加载到内存,最后就可以通过好几个不同的可重定向目标文件生成出一个可执行程序,如下图:

image.png



所以我们可以发现,我们可以通过自己封装一个库文件,然后通过相应的头文件来调用这个库文件,并且将这些文件加载到内存生成可执行文件,最终通过头文件的形式给普通文件使用(例:main.c)


并且注意:我们不仅可以如上图一般,将自己的库文件给当前路径的文件使用,也可以给其它路径下的文件使用,也就是可以把这个库文件给别人使用,相当于语言库和功能库一样,让全世界的人都可以使用,并且为了不把自己的源代码暴露出去,我们也可以将相应的库文件生成一个可重定位的目标二进制文件,然后再通过头文件的方式,让别人去调用,如下图所示:


image.png


如上图,可以看出,当别人需要使用我们实现的库文件时,我们是可以不需要把源文件给它的,可以把库文件生成二进制文件给他,这样他依然可以根据头文件中的接口声明,去调用到二进制库文件中的实现


静态库的封装和使用

如上述所说,可以发现,如果是自己想要使用库文件,或者是别人想要使用我们的库文件,都需要通过当前路径的库文件或者库文件的二进制文件实现,所以使用库文件的前提就是要拥有库文件,然后才是将库文件和目标文件一起进行编译,最后生成可执行程序,所以如果在函数接口非常多的情况下,也就是需要调用的库文件非常多的情况下,这种方式不适用的,因为首先需要去拷贝许多的库文件或者库文件的二进制文件到当前路径之下,然后才可以进行目标文件的编译,否则就不允许你直接使用头文件去调用已经实现了的函数接口,所以这也就是库的由来,为了减少库文件的拷贝,我们可以将这些许多的二进制库文件封装成一个库, 然后以后需要用到的时候,直接将这个库替换到目标文件中就行,不需要一个一个库文件的拷贝,而是很多很多库文件的拷贝,这样就可以大大降低调用库文件的成本,所以具体步骤如下:


静态库打包库文件指令:ar -rc libmymath.a ,然后具体步骤如下图:


image.png


可以发现,当我们想要使用我们自己实现的库时,我们不仅需要有这个库,更重要的是要让编译器可以找到这个库,指令:gcc -o mytest main.c -L. -l mymath

所以像平时我们下载库或者是软件,本质上,这些库和软件的路径都是系统自己规定好的,在使用的时候,系统默认可以直接找到(环境变量知识)


使用第三方库的方式


image.png


如上图,所以我们平时为了给别人使用库,我们一般是先将库文件和头文件放到include和lib文件中,然后将这两个文件压缩成一个压缩包文件,这样就可以让别人更方便的拿到对应的库文件和头文件,所以当其他用户拿到了对应的库文件和头文件时,此时就可以使用它们进行自己目标文件的代码编写了,但是此时由于库文件和头文件都不在gcc或者g++的默认寻找路径中,所以想要直接使用的话,此时就需要像上述指明库的路径一样,将头文件的路径也指明,指令:gcc -o mytest main.c -I ./include -L ./lib -l mymath 具体如下图所示:


50.png


第三方库的使用原则:

  1. 拥有特定的头文件和库文件
  2. 如果没有默认安装到系统gcc或者g++默认的搜索路径下,用户必须指明对应的选项,告知编译器头文件的路径和库文件的路径
  3. 所以对于任何软件,安装和卸载的本质就是将对应软件的库文件和头文件拷贝到系统特定的路径下 (文件系统知识)


当然,你也可以直接自己把头文件和库文件拷贝到系统指定的头文件和库文件路径之下,此时就不需要再指定头文件路径了,系统编译器此时在默认路径下就可以找到,但是由于我们的库不是系统库,还是一个第三方库,所以任然需要指明链接的库的名字,所以将我们下载的库和头文件,拷贝到系统默认路径下,就是等于在Linux系统下安装库


第一方库:语言库

第二方库:操作系统的系统接口

第三方库:我们自己实现的库(即便是已经全部安装到了系统路径中,编译器在寻找的时候,还是需要指明链接的库名称)


由于任何软件,安装和卸载的本质就是将对应软件的库文件和头文件拷贝到系统特定的路径下,所以在下载和卸载软件的时候,就要访问系统特定的路径,所以无论是安装软件还是卸载软件,都是需要提升权限或者使用超级用户


动态库的封装和使用

大致上和静态库的封装使用相同,但是又有一些本质上的不同,如下:


动态库打包库文件指令:gcc -shared -o libmymath.so *.o,发现,编译器自带打包动态库的能力,所以充分说明,在Linux系统中,编译器大部分使用的都是动态库,具体如下图所以:


image.png


第三方库使用方式

如下图,第三方库的使用方式和静态库是相同的,这里不多做讲解

image.png


当我们生成了可执行程序之后,此时如果我们想要运行这个可执行程序,会发现一个问题,如下图

image.png


报错的主要原因是: 我们以为我们已经将库的路径和头文件的路径告诉的操作系统,但是本质上却没有,而只是将头文件和库文件的路径告诉了gcc编译器,所以对于gcc编译器来说,此时它可以生成我们想要的mytest可执行程序,但是对于操作系统来说,此时它就不能让可执行程序变成运行程序,因为我们没有将库文件路径告诉操作系统,所以报错!


动态库不能被操作系统链接解决方法

所以明白了上述问题,此时我们就来解决这个问题吧!但是前提是搞明白一个问题,为什么静态库不会出现操作系统不能链接问题,而动态库会出现操作系统不能链接问题,如下:


原因就是:静态库,链接原则是将用户使用的二进制代码直接拷贝到目标可执行程序中!但是动态库不会,因为动态库并不会直接将二进制代码直接拷贝到可执行程序中,而是让目标程序通过头文件的方式去调用相关库中的函数接口实现


具体解决方法

1.将库路径导入到环境变量中(临时)
2.软连接(永久)
3.配置文件方案

查看一个可执行程序依赖的第三方库指令:ldd 可执行程序,具体如下图所示:

image.png


如上图所示,此时我们发现,操作系统找不到相应的动态库的路径,所以此时我们使用第一种方法,将库路径给导入到环境变量中,指令:

export LD_LIBRARY_PATH=&LD_LIBRARY_PATH:/home/vimtest/其他人的动静态库/我的动态库/lib

这样,此时操作系统就可以找到库所处的路径,然后找到库,此时可执行程序就可以成功运行了


建立软链接

上述导入环境变量的方法,在本质上是一个临时的方法,因为我们都知道,当我们退出Linux系统时,Linux系统中的环境变量会重新刷新,所以上述导入的环境变量会消失,上述的方法就是一个临时的方法,如果想要永久的导入动态库对应的路径到环境变量中,此时就可以使用第二种方法,建立软链接的方法,相信软链接肯定是不陌生,指令:ln -s,本质就是建立一个和原文件有映射关系的新文件,通过新文件就可以找到对应原文件在磁盘中分组的数据块,这样就可以让软链接后的这个常规存在磁盘上的文件一直保留着,我们就可以一直使用该软链接文件对应数据块中的动态库路径了


配置文件方案

这个内容较多,我们留到下篇博客学习


image.png


总结:动静态库的知识非常多,并且涉及用户层面和系统层面,延伸知识非常广泛!

相关文章
|
6月前
|
Linux 编译器 C语言
《Linux从练气到飞升》No.05 Linux编译器gcc/g++的使用及编译过程 【云边有个小卖部】上新
《Linux从练气到飞升》No.05 Linux编译器gcc/g++的使用及编译过程 【云边有个小卖部】上新
156 0
|
5月前
|
C语言
C语言小插曲——对图形库的初步探索(消除小球小游戏)
C语言小插曲——对图形库的初步探索(消除小球小游戏)
32 0
|
6月前
|
Linux
《Linux从练气到飞升》No.20 Linux进程替换
《Linux从练气到飞升》No.20 Linux进程替换
41 0
|
Linux API C语言
Linux下C语言实现弹弹方块小游戏
Linux下C语言实现弹弹方块小游戏
123 0
|
存储 Linux 定位技术
学习系统编程No.14【动静态库】
学习系统编程No.14【动静态库】
|
存储 Shell Linux
学习系统编程No.4【环境变量】
学习系统编程No.4【环境变量】
|
Linux C语言
Linux下C语言编写贪吃蛇小游戏源码
Linux下C语言编写贪吃蛇小游戏源码
325 0
Linux下C语言编写贪吃蛇小游戏源码
|
存储 前端开发 编译器
C生万物 | C语言文件操作指南汇总【内附文件外排序源码】
从0到1教你学会C语言文件操作。附有文件外排序实战训练加成,对文件操作更上一层楼
125 0
C生万物 | C语言文件操作指南汇总【内附文件外排序源码】
|
算法 程序员
蓝桥杯单片机快速得奖方法(分享一些实用技巧)
蓝桥杯单片机快速得奖方法(分享一些实用技巧)
346 0
|
自然语言处理 编译器 Linux
【Linux篇】第十一篇——动静态库(动静态库的介绍+动静态库的打包与使用)
【Linux篇】第十一篇——动静态库(动静态库的介绍+动静态库的打包与使用)
【Linux篇】第十一篇——动静态库(动静态库的介绍+动静态库的打包与使用)