环境:centos7.6,腾讯云服务器
Linux文章都放在了专栏:【 Linux 】欢迎支持订阅 🌹相关文章:
【Linux】动静态库以及动静态链接
【Linux】基础IO_文件系统
软硬链接
软链接
我们知道,每一个文件都会有自己的inode编号,我们可以通过如下指令来查看:
ls -l -i
我们发现,每一个不同文件的inode编号都不相同,所以inode可以说是用来标识文件的标识符。接下来,我们通过下面指令来给mysoft文件,创建软链接:
ln -s mysoft mysoft-s
我们发现,创建出来的软链接文件的inode编号与源文件并不相同,并且,软链接文件的大小远远小于源文件的大小。实际上,软链接又称为符号链接,软链接文件是一个独立的文件,有自己的inode属性以及内容,软链接文件的内容就是只包含了源文件的路径名称。因此大小要比源文件要小很多。这就类似于Windows下创建的快捷方式文件。
当然,假如我们将源文件删除或者改变源文件路径后,那么软链接文件也就运行不了,因为软链接文件内仅仅只是源文件所在的路径,当源文件不在时,软链接文件也就“失效了”。如下所示:
硬链接
接下来我们来看硬链接,我们可以通过下面命令给mylink文件创建硬链接:
ln mylink mylink-h
我们发现,创建出来的硬链接文件不管是大小,还是inode编号都与源文件相同,并且那个数字也从1变成了2。可以看出来,硬链接代表的意义就是给源文件起一个“别名”,也就是说,虽然它们名字不同,但代表的都是同一个文件,因为它们的inode编号相同!。而这个数字2,就是用来统计一个文件究竟有几个“别名”。因此这里变成了2。
当然,假如此时我们将源文件的路径给更改,并不会发生什么影响,但是假如我们将源文件给删除。我们会发现,那个数字就会由2变成1,但依然可以运行。
接下来我们看一个现象:为什么目录文件硬链接数为2?
答案是很简单,因为每一个目录文件,即使是个空目录,里面也一定有如下内容:当前路径文件.和上级路径文件..。
当然,假如我们在B目录文件下再建立一个新的路径文件,那么B的硬链接数就会由2变成3,因为新的路径文件下都会有一个..文件,..标识上级路径,也就是B,因此B的硬链接数会由2变3。
这里有一点需要注意:我们可以给普通文件创建硬链接,但不能给目录文件创建硬链接,因为假如能给目录文件建立硬链接,就容易发生环路路径问题。
软硬链接的区别
- 软链接又称为符号链接,是一个独立的文件,有单独的inode编号,该文件的内容为目标文件的路径。
- 硬链接是将不同的文件名关联到同一个inode节点,名字不同,但都是指同一个文件。
- 软链接可以给目录创建,但硬链接不可以给目录创建
- 删除原目标文件后,软链接文件会收到影响,会“失效”,但硬链接文件不受影响,依然可以正常运行,仅仅只是硬链接数-1。
- 硬链接的文件属性类型与原文件保持一致,而软链接文件的属性类型为l,l表示链接文件
- 软链接的大小很小,硬链接的大小与原目标文件一致,因为硬链接文件本身就是原目标文件的“别名”。
动静态库
什么是库文件?
我们在编写C/C++代码时,实际上一直都在用库(c/c++标准库),在编写代码时,有很多库函数诸如printf等,我们为什么能直接拿来用呢?是因为我们包含了各自对应的头文件,而头文件的内容包含了该函数的声明,具体的实现方法则在库文件中,在链接阶段,我们经过编译后的.o文件会与库文件进行合并,最终形成可执行程序。
因此,可以这么来说:库文件中提供函数的实现方法,而头文件中提供函数说明。两者配套使用。实际上,库其实就是大量方法文件形成的.o文件的集合。
为什么要存在库?
库的存在就是为了提高开发效率,举个例子,假如没有c/c++标准库,我们在写代码时就要手动将printf、cout等高频函数的实现方法进行编写,这样就大大减少了我们的开发效率。
而且假如在日常开发中,假设别人想要使用我们实现的一些接口,但是我们又不想让别人看到我们是如何实现的,此时我们就可以将接口的实现打包成一个库,然后直接将库文件和对应头文件发送给对方即可。
库又分为动态库和静态库,两者的优缺点在之前的文章已经详细讲解<<点击跳转>>,这里主要讲如何库的使用和原理。
如何制作和使用第三方库
第一方库:语言提供的库(如c/c++标准库)
第二方库:操作系统提供
第三方库:other提供,比如我们接下来自己制作的动静态库
静态库的打包
静态库的打包主要分为两个步骤:
- 将存放方法的源文件进行编译,编译后(含预处理--编译--汇编)生成.o为后缀的可重定位二进制目标文件。(gcc -c)
- 将所有的.o文件使用ar -rc指令,进行打包形成静态库。
- 将静态库与头文件压缩后发送给他人即可供他人使用
这里我简单举个例子:
假如我自己写了一个Add和Sub接口的实现,然后其他人想直接用我们的接口,此时我们想在不将方法的具体实现暴露出来,仅仅是将接口的功能给他人使用,此时我们就可以打包成库,发送给别人即可。如下图:
第一步:使用g++ -c的指令,将方法实现的源文件进行编译后生成.o结尾的可重定位二进制文件:
第二步:使用ar -rc 指令将所有的.o文件进行打包
注意:ar是gnu归档工具,通常用指令ar -rc来进行静态库打包。而ar -tv指令则可以查看静态库的内容。如下:
接下来,我们再将所有的头文件都放在同一个文件下,如下所示:
第三步:使用tar指令将库文件与头文件进行压缩,然后发送给其他人 使用即可
静态库的使用
此时我们切换身份,我们作为otherPeople,我们想要使用这个静态库,该如何使用呢?
第三方库的使用规则
首先,任何第三方库的使用,必须在编译时要标注三个要素:库所在的路径、对应头文件的路径、要链接的库名(库名需要去掉前缀与后缀)。
第一种方式使用静态库:编译时手动指定
gcc/g++编译选项 | 含义 |
-L | 指定库所在的路径 |
-I(大写i) | 指定头文件所在路径 |
-l(小写L) | 指定库名称(去掉前后缀) |
如下,假如我要使用这个静态库,我先将这个压缩包解压:
接下来我们进行g++编译,这里编译时我们手动指定所需要的库名(-l)、库路径(-L)、头文件路径(-I)。
对于静态库的使用,还有第二种方法如下:
第二种方式使用静态库:将头文件以及库文件安装在系统目录
由于gcc/g++在编译时,会默认去系统目录搜索,进行路径匹配,这也是为什么我们平常用c/c++标准库时,编译时不需要再指定库路径和头文件路径。
库所在系统路径:/lib64
头文件所在系统路径:/usr/include
这里需要注意的是,一般我们不要轻易修改系统的环境,因此我们用完后要手动删除,否则会一直存在。
动态库的打包
上面讲了静态库的打包和使用,接下来将动态库的打包和使用,以及动态库链接的原理。
动态库的打包分为以下几个步骤:
- 将存放方法的源文件进行编译,编译后(含预处理--编译--汇编)生成以.o为后缀的可重定位二进制目标文件。同时在编译时生成与位置无关码。(gcc/g++ -c -fPIC)
- 直接gcc/g++对所有的.o文件进行编译,同时加上-shared选项,打包成动态库即可。(gcc/g++ -shared)
- 将动态库与头文件压缩后发送给他人即可供他人使用
以上静态库例子打包成动态库,步骤如下所示:
紧接着我们可以将动态库与头文件进行压缩,将压缩包给other用户,供他人使用。
这里有一点需要注意,就是我们一般会把头文件,单独放在一个目录,库文件单独放在一个目录。(上面静态库的例子忘记了,这里说一下。)
动态库的使用
接下来我们已other的身份,进行使用动态库,我们先将压缩包进行解压,然后进行编译,编译时指定头文件、库文件的路径,以及库名。
虽然可以正常编译,但是此时我们运行可执行程序,就会出现如下报错:
这是因为我们在编译时,仅仅只是告诉了编译器,但是OS并不知晓,OS只会在系统路径,以及当前所在路径下进行查找头/库文件。因此此时会报错。(静态链接并不会,因为生成的可执行程序的运行,不会依赖库),这时常用的解决方法有如下几种:
1、将库文件拷贝到系统路径
此时我们假如将我们的第三方动态库,拷贝到系统路径/lib64下,即可正常运行。如下:
需要注意的是,我们一般不会对系统环境进行更改,用完后要进行删除,否则的话,下一次重新登陆,系统路径下还是会存在我们自己的第三方库。
2、将库路径导入环境变量LD_LIBRARY_PATH中
用export指令,将库路径(绝对路径)导入环境变量LD_LIBRARY_PATH中,如下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qidunyan/linux-exercise/test_23_06/test_23_0607/otherPeople/mylib/
此时程序依然可以正常运行
该方法的好处就是用完后,我们不需要对该环境变量进行删除库的路径,因为下次重新登陆时,该环境变量会被更新。
3、对系统配置文件/etc/ld.so.conf.d进行更改
系统配置文件/etc/ld.so.conf.d中存放的都是以.conf为后缀的文件,该文件内存放的是路径。我们只需要将动态库的绝对路径,放在一个以.conf为后缀的文件中,再将该文件拷贝到系统配置文件内即可。如下:
但是此时运行的话,依然会显示失败,因为我们没有对配置文件进行更新,我们只需要输入指令:ldconfig,进行更新配置文件即可。
当然,我们为了不污染系统环境,使用完后也要记得手动删除,否则会一直存在。
动静态库链接的原理
为什么静态链接生成的可执行程序,不会依赖库文件呢?因为在编译阶段会将库中方法的代码加载到可执行程序中,这样就会出现一个情况,假如同一个方法比如printf,被调用了多次,这也就会导致printf实现的代码,被重复复制了多次,出现大量冗余重复的代码,这也就是为什么静态链接生成的可执行程序体积大小非常大的原因。
而动态链接则不是这样,程序在链接动态库时,会通过库的起始地址+偏移量,来找到函数方法所在的位置,而这个偏移量,就是我们生成的与位置无关码。其实就是0 1 2 3 4...这样的偏移量,也就是说,只要知道库的起始地址,就能根据0 1 2 3...这样的偏移量,来找到各自的方法。为什么叫与位置无关码呢?因为库被映射到地址空间的地址是不确定的,但是偏移量是固定的,这样不管库被映射到哪个地址,通过偏移量都可以找到函数所在的位置。(举个例子,假如我对你说,我距离你10米远,那么不管你的位置在哪里,只需要从你的位置+10米,就可以找到我,这个10米,就类似位置无关码)
而在程序运行时,动态库会被加载到物理内存,同时会通过页表映射到进程对应的地址空间中的共享区。此时动态库的地址也就有了。且同一个方法,它的库地址+偏移量相同,所以代码只需要存在一份即可,避免了代码冗余。同时假如存在多个进程同时运行且使用同一个库,那么动态库也只需要在内存中加载一份,然后映射到各自的共享区,通过库地址+偏移量就可以跳转到方法的实现。大大节省了空间的使用。
补充
云服务器默认只存在动态库,因此我们若想使用C/C++静态库,需手动安装
安装C/C++静态库
sudo yum install -y glibc-static
sudo yum install -y libstdc++-static
另外,我们需要知道以下几点:
gcc/g++默认采用动态链接,但是假如只存在静态库,则gcc/g++只会进行静态链接,同样,只存在动态库,也只能进行动态链接(即使我们加上 -static)。
而若动静态库同时存在,则gcc/g++会默认进行动态链接。也可以手动指定进行静态链接(-static)
end.
生活原本沉闷,但跑起来就会有风!🌹