目录
0.文件系统
1.软硬链接
2.静态库
2.1先见一见
2.2 制作静态库,并使用制作的静态库
3.动态库
3.1制作动态库,并使用制作的动态库
4.推荐一个第三方库(ncurses)
5.动态库的加载
6.动态库VS静态库
0.文件系统
Linux--文件系统-CSDN博客
1.软硬链接
Linux--软硬链接-CSDN博客
2.静态库
在Linux中:.so(动态库) .a(静态库)
2.1先见一见
在这里我们写了一段代码:
我们在执行这段代码的时候,调用了里面的函数,我们没有该函数的实现,但可以使用它,是因为我们链接了它们的库,有gcc编译器完成链接,ldd可以查看当前可执行程序链接了上面库。而(libc.so.6 => /lib64/libc.so.6 (0x00007fb7ab942000))这个库就是C语言的标准库。
这是它们的软链接关系
这是C语言的库文件
2.2 制作静态库,并使用制作的静态库
1.我们先将自己提供的目标文件,形成后缀为.o的文件(将源代码为编译对象文件)(
.o
文件通常指的是“对象文件”(Object File)。这些文件是编译器(如GCC)将源代码(如.c
、.cpp
等文件)编译后产生的中间文件,但它们还不是可以直接运行的可执行文件。)以下是我提供的C语言文件
现在我们要生成对象文件,指令为:
gcc -c 文件名.c -o 文件名.o
2.创建静态库(使用
ar
命令将对象文件打包为一个静态库文件(.a
文件))命令:ar rc libmyc.a 对象文件
r
选项表示替换现有的目标文件,c
选项表示创建一个库。也就是说你要我形成的这个库如果原本没有,我就帮你形成一个新的库,如果有了那我就把你原来旧的库替换掉。3.将我们创建的静态库安装到系统下:
1.为了达到与C语言库一样的效果,我们要将我们的头文件拷贝到系统级的头文件目录下:
sudo cp mystdio.h mymath.h /usr/include/
2.将对象文件拷贝到C语言标准库的目录下:
sudo cp libmyc.a /lib64
4.写一段C语言代码并调用自己的库:
我写的mymath是实现myAdd函数的,mystdio对几个文件操作函数的实现。
接着我们来编译这段代码,看行不行:发现找不到这些函数,这是因为写到C/C++的库,gcc/g++默认是认识C/C++的库,libmyc.a --.>别人写的(第三方库)->gcc/g++是不认识的
在编译的时候要加 -l(limbmyc.a),表示在编译的时候要链接这个库。结果还是找不到。
这是因为要去掉前缀lib,后缀.a,myc才是库真实的名字。
至此可知:安装库就是将第三方的头文件,库文件拷贝到系统指定的目录下。
如果不想加载到指定目录下的写法:
gcc main.c - I(大写的i)./(指定用户自定义头文件路径)-L ./(指定用户自定义库文件路径)-l(执行确定的第三方库名称)
通过使用库,开发者可以避免编写大量重复的代码。库中的函数、类和方法都是经过测试和优化的,因此可以直接在项目中使用,而无需担心潜在的错误或性能问题。
3.动态库
3.1制作动态库,并使用制作的动态库
1. 编译为位置无关代码
这条指令做了两件事情:
-fPIC
选项:它告诉 GCC 生成位置无关的代码(Position Independent Code),这种代码可以在内存中的任何位置运行,这对于动态库(shared libraries)和某些类型的程序(如 PIE - Position Independent Executables)来说是必要的。
-c
选项:它告诉 GCC 只编译源文件,不链接它。这会产生一个目标文件(object file),通常是.o
扩展名,该文件包含了编译后的机器代码,但还没有和其他目标文件或库链接在一起2. 链接为目标共享库
直接用gcc就可以 了。命令:
gcc -shared *.o -o libmyc.so(将多个编译过的目标文件(
.o
文件)链接成一个动态共享库)3.使用自己的态库
gcc test.c - I(大写的i)./(指定用户自定义头文件路径)-L ./(指定用户自定义库文件路径)-l(执行确定的第三方库名称)
执行下面程序:
此时使用gcc编译main.c生成可执行程序时,需要用
-I
选项指定头文件搜索路径,用-L
选项指定库文件搜索路径,最后用-l
选项指明需要链接库文件路径下的哪一个库。gcc test.c -I mylib/include -L mylib/lib -lmyc
与静态库的使用不同的是,此时我们生成的可执行程序并不能直接运行。
需要注意的是,我们使用
-I
,-L
,-l
这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁,但是当生成的可执行程序生成后就与编译器没有关系了,此后该可执行程序运行起来后,操作系统找不到该可执行程序所依赖的动态库,我们可以使用ldd
命令进行查看。可以看到,此时可执行程序所依赖的动态库是没有被找到的。而静态库在编译期间,已经将库中的代码拷贝到我们的可执行程序内部了!加载和库就没有关系了!
4.让操作系统找到运行程序所依赖的动态库的四个方法
方法一:拷贝.so文件到系统共享库路径下(不建议)
既然系统找不到我们的库文件,那么我们直接将库文件拷贝到系统共享的库路径下,这样一来系统就能够找到对应的库文件了。
sudo cp mylib/lib/libmyc.so /lib64
方法二:软链接
ln -s /home/light/mystdio/mylib/lib/libmyc.so /lib64/libmyc.so
这条命令在
/lib64/
目录下创建了一个名为libmyc.so
的符号链接,/lib64/libmyc.so
: 这是链接文件的路径。这不会复制libmyc.so
到/lib64/
目录下,而只是在该目录下创建一个指向/home/light/mystdio/mylib/lib/libmyc.so
的链接。在运行就成功了方法三:环境变量法(更改
LD_LIBRARY_PATH)
LD_LIBRARY_PATH
是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH
环境变量当中即可。export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/light/mystdio/mylib/lib
LD_LIBRARY_PATH
是一个由冒号分隔的目录列表,动态链接器会在这些目录中查找动态链接库。
LD_LIBRARY_PATH
: 这是一个环境变量,用于指定动态链接器搜索共享库的额外目录。
$LD_LIBRARY_PATH
: 通过$
符号引用现有的LD_LIBRARY_PATH
环境变量的值。
:/home/light/mystdio/mylib/lib
: 这里,:
是目录分隔符,后面跟的是你想要添加到一旦你关闭终端或者重启机器,所有的环境变量设置都会丢失,因为它们只存在于当前 shell 进程的上下文中。
如果你希望在每次登录或重启后都能保持这个环境变量的设置,你需要将这个命令添加到某个在登录时自动执行的脚本中。对于 bash shell,通常这样的脚本是用户的
.bashrc
、.bash_profile
或.profile
文件(具体哪个文件取决于你的系统配置和个人偏好)。只要使用vim打开,将之前设置的环境变量添加进来,登录或重启后都能保持这个环境变量的设置
方法四:配置
/etc/ld.so.conf.d/(三步走)
我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题,/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。
1.首先将库文件所在目录的路径存入一个以.conf为后缀的文件当中。
2.然后将该.conf文件拷贝到
/etc/ld.so.conf.d/
目录下。但此时我们用
ldd
命令查看可执行程序时,发现系统还是没有找到该可执行程序所依赖的动态库。3.这时我们需要使用
ldconfig
命令将配置文件更新一下,更新之后系统就可以找到该可执行程序所依赖的动态库了。此时我们就可以正常的运行程序了
4.推荐一个第三方库(ncurses)
ncurses是一个功能强大且广泛使用的库,它使得开发者能够在字符终端上创建出丰富、交互式的用户界面。通过其提供的API和核心概念如屏幕缓冲区、颜色系统和窗口管理等,开发者可以轻松地构建出各种复杂且吸引人的终端应用程序。
先安装这个库
使用到这个库sh,编译时记得指定
5.动态库的加载
静态库在编译期间,就把库中的方法拷贝到了程序当中,未来加载就只谈论程序加载,与库没有关系,所以我们再谈库的加载时,不考虑 静态库,只考虑动态库。
动态库加载的整体轮廓:
每个进程都有其独立的虚拟地址空间,由操作系统管理。地址空间包括代码段、数据段、堆和共享区等区域。当可执行程序启动时,操作系统会检查其依赖的动态库。动态链接器(如Linux中的ld.so或ld-linux.so)负责将动态库加载到进程的地址空间中的共享区。库中的代码和数据会被映射到相应的虚拟地址上,使得程序能够通过指针或函数调用访问它们。多个进程是可以同时使用一个动态库的,因为都在一个内存里,其它进程同样只需要将动态库加载到自己地址空间的共享区中,就能使用这个动态库。
我们的可执行程序,编译成功,没有加载运行,二进制代码中有“地址”吗?
当然包含了地址。源代码会首先被转换为汇编代码,然后汇编器将这些汇编指令转换为机器代码。在这个过程中,汇编器会为程序中的各个部分(如函数、变量等)分配相对地址或偏移量。这些地址用于在程序内部进行跳转、调用或数据访问(每条汇编语句都有它的地址)。
编译后的二进制代码中确实包含地址信息,但这些地址通常是相对地址或偏移量,用于在程序内部进行定位。这些地址在程序被加载到内存并执行时,才会被转换为实际的物理或虚拟内存地址。(cup的pc指针拿到虚拟地址,通过查页表找到物理地址,然后执行代码)
以上都是程序的加载
再理解动态库的加载
- 当程序开始运行时,如果它依赖于动态库,那么这些库的代码和数据并不会立即被加载到物理内存中。相反,当程序首次调用库中的函数或访问库中的数据时,动态链接器会负责加载所需的库。
- 动态链接器首先检查所需的库是否已经在内存中加载。如果是,则通过共享区的映射直接使用该库;如果不是,则从磁盘中加载库文件到物理内存,并更新页表以建立虚拟地址和物理地址之间的映射关系。
- 动态库被加载到内存中后,会被映射到进程的地址空间中。这意味着库中的代码和数据现在可以通过进程的虚拟地址空间进行访问。
- 由于多个进程可能共享同一个动态库,因此操作系统会确保这些进程在各自的地址空间中都能正确地映射到库的实际物理地址。这是通过共享内存技术实现的,其中多个进程可以访问同一块物理内存区域。
6.动态库VS静态库
在上面制作的库中,动静态库同名,运行程序时,系统时默认优先使用动态库的。
在编译和链接程序时,通常会明确指定使用静态库还是动态库。例如,在GCC编译器中,可以通过
-static
选项来强制使用静态链接,或者使用-l
选项后跟库名来指定要链接的库。如果同时指定了同名的静态库和动态库,编译器和链接器的具体行为可能因系统和工具链的不同而有所差异。动静态库的差异:
1. 链接时间和方式
- 静态库:在编译时被链接到可执行文件中。静态库的代码会被完整地复制到最终的可执行文件里。
- 动态库:在运行时被加载到内存中。动态库在编译时不会被合并到可执行文件中,而是在程序运行时由系统动态加载。
2. 对可执行文件大小的影响
- 静态库:由于静态库的代码被完整地复制到每个使用该库的可执行文件中,因此会导致可执行文件体积增大。
- 动态库:多个程序可以共享同一个动态库,因此不会显著增加每个可执行文件的大小。
3. 更新和维护
- 静态库:如果静态库发生更新,所有使用该库的可执行文件都需要重新编译和链接。
- 动态库:当动态库更新时,只要库中的函数定义和接口没有变化,使用该库的可执行文件通常不需要重新编译或链接。这大大简化了软件的更新和维护过程。
4. 依赖性和移植性
- 静态库:由于代码在编译时已被嵌入到可执行文件中,因此静态库对可执行文件的依赖性较低,移植相对方便。
- 动态库:程序在运行时需要动态库的支持,如果目标系统上没有相应的动态库,程序将无法运行。这增加了对运行环境的依赖性,但在多个程序共享相同库的情况下,可以节省内存和系统资源。
5. 文件扩展名
- 静态库:在Unix/Linux系统上通常以
.a
为文件扩展名,在Windows系统上通常以.lib
为文件扩展名。- 动态库:在Unix/Linux系统上通常以
.so
(Shared Object)为文件扩展名,在Windows系统上则以.dll
(Dynamic Link Library)为文件扩展名。综上所述,动态库和静态库各有优缺点,适用于不同的场景和需求。静态库更适合于需要独立运行、对体积不敏感且更新频率较低的应用;而动态库则更适合于需要频繁更新、多个程序共享代码以及对可执行文件大小有严格要求的应用场景。