赞
踩
目录
因为我们一定会使用别人的库与组件。
动态库 .so -> 动态链接:我们的程序和库产生关联,我们执行的时候需要指定库函数调用,直接跳指定库中去执行。执行完就返回。体积小
静态库 .a -> 静态链接:将静态库中的内容拷贝到我们的可执行程序中。体积大
Note:库里面不能放mian函数,因为库是给别人用的,如果有会导致与使用者的main函数产生冲突。
当我们对于所写的源文件和头文件在Linux下进行处理变为可执行程序需要一下四步:
预处理:完成包含的头文件的展开、注释删除、define宏替换、条件编译等,最终形成 xxx.i 文件。
编译:完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成 xxx.s 文件。
汇编:将汇编指令转换成二进制指令,形成符号表,最终形成 xxx.o 文件。
链接:将生成的各个 xxx.o 文件进行链接,合并段表,符号表的合并和重定位,最终形成 .exe(.out) 可执行程序。
提供方法:
当我们相直接使用执行的时候,可以直接用main.c进行调用。
通过将所有的.c文件变为可执行程序 .exe 文件。
实际上,我们对于库的操作是基于3到4步。库的本质就是将提供方法的目标文件(xxx.o)进行集合,以达到库当中并不包含主函数而只是包含了大量的方法,所以说动静态库本质是可执行程序的 “半成品集合” 。
因为只将 .o 与 .h 文件给与别人时就能够使用,但是直接全部给别人,这个量又太大了,看着一堆 .o ,重要的是编译的时候全要带,更重要的是万一漏了一个,所以需要将所有的进行打包 -- 这个过程就叫做形成静态库。
本质上: .o 打了一个包。
命令:ar -rc libtest.a 所有的.o文件
ar(Archive 归档) -r(replace 替换) -c(create 创建)
将所有.o打包成一个libxxx.a的静态库。
node:库的名字前缀必须是 lib 名字随便取,后缀必须是 .a
利用Makefile封装静态库
- libtest.a: mymath.o myprint.o
- ar -rc libtest.a mymath.o myprint.o
- mymath.o: mymath.c
- gcc -c mymath.c -o mymath.o
- myprint.o: myprint.c
- gcc -c myprint.c -o myprint.o
-
- .PHONY:clean
- clean:
- rm -rf *.o libtest.a
静态库有了,就要考虑如何发布。
所以需要将Makefile进行修改
libhello.a: mymath.o myprint.o ar -rc libhello.a mymath.o myprint.o mymath.o:mymath.c gcc -c mymath.c -o mymath.o myprint.o:myprint.c gcc -c myprint.c -o myprint.o .PHONY:hello hello: mkdir -p hello/lib mkdir -p hello/include cp -rf *.h hello/include cp -rf *.a hello/lib .PHONY:clean clean: rm -rf *.o libhello.a hello
系统头文件 gcc 的默认搜索路径在 /user/include 下,包含的是所有的 .h 。
系统库文件的默认搜索路径是 /lib64 或 /usr/lib64 。
将发布的hello中的库文件与头文件分别导入到系统中。
又因为,我们自己所写的库是,属于第三方库(不是语言提供的,也不是操作系统自带的)。系统自带的可以自动运行。语言提供如gcc、g++默认就能连接语言的标准库。第三方库必须告诉gcc我们要连接的库是哪一个库。
命令:gcc main.c -ltest
-l 后跟库名( libhello.a 处去 lib 与 .a ) 告诉gcc我们要连接的是 libhello.a
我们刚刚的拷贝库到系统的默认路径下,就叫做库的安装。
融会贯通的理解:
无论是在windows还是Linux下安装软件,本质上就是拷贝。将软件拷贝到指定路径下。(安装软件 -本质-> 拷贝软件)
不建议直接将库拷贝到系统的路径下,因为我们写的软件没有经过可靠性的验证的, 可能会出现污染其他的文件,所以此处学习完要立马删除。(这个过程就叫做卸载)
复习:
gcc的查找文件的默认路径为先当前路径,后系统路径。
当前路径是一个目录下的文件。我们的头文件是在include目录下与库文件是在lib目录下,所以此处不是与 a.out 同一个路径。
命令:gcc main.c -I ./hello/include/ -L ./hello/lib
-I(大写的i)(include) gcc在默认路径(当前路径、系统路径)下找不到头文件,那么就到我们给与的路径下查找。
-L(library) gcc在默认路径(当前路径、系统路径)下找不到库文件,那么就到我们给与的路径下查找。
但是会发现此处还是错误。
Note:因为我们并不能保证 ./hello/lib 下,一定只有一个库。很有可能其中含有大量的库。所以我们还需要将库进行指明 -lhello 。(库去掉前缀和后缀)
gcc main.c -I ./hello/include/ -L ./hello/lib -lhello
总结ls的操作:
- -I(大写i) 头文件搜索路径
- -L 库文件搜索路径
- -l库名 特定路径下,使用哪一个库
动态库也需要将 .c 变为 .o 文件
命令:gcc -fPIC -c mymath.c -o mymath.o
-fPIC(大写的i) 形成与位置无关的目标二进制文件。
与位置无关的目标二进制文件:
融汇贯通的理解:
- 静态库在编址的时候,必须按照可执行程序的编址按序进行。(绝对编址)即需要考虑地址空间,固定的加载位置。
- 动态库在编址的时候,不用根据可执行程序的编址按序进行。(相对编址)即不需要考虑地址空间,只需要知道代码在什么地方开始调用动态库,随意的加载位置,相对的加载位置。
例如:一把尺子
对于静态库的描述是在刻度30上。对于动态库的描述是尺子的某刻度位置有一个标志物,相对于标志物的距离是多少(段地址加偏移的方式)。
于是如果尺子左右衍生,就会导致刻度失效 -> 绝对编制可能会失效、相对编址一定不会失效。
意义:
可执行程序存储的地址空间就是尺子,动态库就是尺子上的标志物。
用静态库实行可执行层序时,是将静态库拷贝进代码中,放入地址空间中采取如尺子上绝对路径的方式。
动态库形成库和可执行程序是两码事,可执行程序通过地址调用动态库,再通过加偏移找到。
命令:gcc -shared myprint.o mymath.o -o libhello.so
-shared 告诉gcc形成的是动态库,不是可执行程序。
将所有.o打包成一个libxxx.so的静态库。
node:静态库的名字前缀必须是 lib ,名字随便取,后缀必须是 .so
利用Makefile封装动态库
- libhello.so: mymath.o myprint.o
- gcc -shared mymath.o myprint.o -o libhello.so
- mymath.o:mymath.c
- gcc -fPIC -c mymath.c -o mymath.o
- myprint.o:myprint.c
- gcc -fPIC -c myprint.c -o myprint.o
-
- .PHONY:clean
- clean:
- rm -rf *.o libhello.so
动态库有了,就要考虑如何发布。
所以需要将Makefile进行修改
libhello.so: mymath.o myprint.o gcc -shared mymath.o myprint.o -o libhello.so mymath.o:mymath.c gcc -fPIC -c mymath.c -o mymath.o myprint.o:myprint.c gcc -fPIC -c myprint.c -o myprint.o .PHONY:hello hello: mkdir -p hello/lib mkdir -p hello/include cp -rf *.h hello/include cp -rf *.so hello/lib .PHONY:clean clean: rm -rf *.o libhello.so hello
方法与静态库的一样。
当我们将静态库和动态库,放在一起进行发布时。
补充:
利用Makefile同时发布静态库、动态库。
.PHONY:all all:libhello.so libhello.a libhello.so:mymath_d.o myprint_d.o gcc -shared mymath_d.o myprint_d.o -o libhello.so mymath_d.o:mymath.c gcc -c -fPIC mymath.c -o mymath_d.o myprint_d.o:myprint.c gcc -c -fPIC myprint.c -o myprint_d.o libhello.a: mymath.o myprint.o ar -rc libhello.a mymath.o myprint.o mymath.o:mymath.c gcc -c mymath.c -o mymath.o myprint.o:myprint.c gcc -c myprint.c -o myprint.o .PHONY:hello hello: mkdir -p hello/lib mkdir -p hello/include cp -rf *.h hello/include cp -rf *.a hello/lib cp -rf *.so hello/lib .PHONY:clean clean: rm -rf *.o libhello.so hello
#问:这个时候main.c使用的是静态库还是动态库?
命令:ldd 可执行文件
用来查看一个可执行程序所依赖的动态库文件。
gcc默认对动态库进行动态连接。如果将动态库移除只保留静态库。
此时其连接的是静态库,而其他的连接的是动态库。-> 如果是动静态库同时存在的时候,默认先连接动态库,如果只有静态库,那么就只能连接静态库。
如果动静态库同时存在,又非要使用静态库。
命令:gcc main.c -I ./hello/include/ -L ./hello/lib -lhello -static
-static 摒弃默认优先使用动态库的原则,而是直接使用静态库的方案
知晓了默认是优先连接动态库,但是我们连接动态库会发现并不能运行。
在此处可以发现,可执行程序 a.out 已经形成,说明找不到动态库是并不会影响可执行程序的形成,但是运行的时候会报错。
动态库的加载
动态库是一个独立的文件,其与可执行程序是进行分批加载,如图可执行程序在内存的一个区域内,动态库在内存的另一个区域内,而可执行程序通过存储动态库的符号链接使用动态库。
可执行程序程序对于动态库的调用,是通过栈区与堆区之间共享区存储的动态库的方法。
补充:
如果是静态库,a.out里面直接就包含了库代码,直接加载a.out到内存里,我们的代码和库的代码就同时加载到内存里了。(是一个统一进程的代码和数据以统一使用)
当多个进程需要使用动态库的时候,如上已经由一个进程加载时,将动态库加载到了内存中,那么后序的进程无需再次加载动态库,直接与动态库建立映射关系即可。所以一个动态库可以被很多个进程共同使用 -> 叫做共享库。
补充:
由于静态库不是共享库,需要拷贝进入可执行程序,所以如果有10个进程同时使用一个静态库,需要每一个奖进程都将静态库拷贝如可执行程序,就会导致内存中有9份一摸一样的代码。并且这些空间还是只读的 - 空间的浪费。(此处凸显了:共享库的意义)
现在再让我们回头看一下之前的报错:
#问:此处的错误是:找不到动态库。但是此处我们使用了 -l库名 告诉了是哪一个动态库,去报找不到该库。这是为什么?
因为,根据前面所讲,动态库和可执行程序是分批加载的,所以在加载可执行程序后,需要将动态库单独加载入内存,但是此时我们告诉的是gcc该动态库在哪,而本质上运行加载的时候和gcc并没有关系(当准备加载动态库进行运行的时候,可执行程序已经形成了,此时就跟gcc没任何关系了)。是需要操作系统加载器进行加载,需要告诉系统。
环境变量:LD_LIBRARY_PATH
LD(load 加载) LIBRARY(library 库) PATH(path 路径)
动态库加载的搜索路径。
系统搜索对应的动态库路径时,会在系统的 /lib64 进行搜索,找到动态库就用,找不到直接终止。如果默认路径/lib64下没有找到,如果LD_LIBRARY_PATH中不为空,就去其中搜索。
Note:千万不要直接将路径带上,这样会将LD_LIBRARY_PATH中原生的东西直接覆盖掉。
命令:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qcr/.VimForCpp/vim/bundle/YCM.so/el7.x86_64(:加动态库的绝对路径)
将LD_LIBRARY_PATH的原生内容与追加内容放在LD_LIBRARY_PATH中。
Note:这个导的环境变量是内存级的环境变量,一旦退出再重新登陆,LD_LIBRARY_PATH就会自动到系统配置文件里拿数据,就将之前的清掉了。 -- 这个方法是临时方案。
在系统里面修改配置文件。
系统会存在一个动态库的默认搜索库文件:
命令:ls /etc/ld.so.conf.d/
/etc/ld.so.conf.d/ 这个路径里面会保存可以允许自定义配置搜索动态库路径 -- 永久解决方案。
也就是所,当觉得一个动态库非常的重要(常用),但是又不想将其安装到系统里面,需要写一个配置文件告诉系统怎么做。很简单:
在路径 /etc/ld.so.conf.d/ 下开辟一个文件,里面存储我们的需要放入的动态库的绝对路径即可。
命令:sudo ldconfig
将该路径 /etc/ld.so.conf.d/ 下的配置的所有配置文件生效一下。
此时 LD_LIBRARY_PATH 中并没有存储我们的动态库的地址,但是可以使用。甚至重新登陆照样也可以。
这就是新增配置文件的方法,一劳永逸的方法。
Note:此处一定要使用绝对地址,不要使用相对地址。找不到就会一直飘红,相当于快捷快捷方式在可执行程序没了。
需要删除重新进行设置。
库的操作是很复杂的,所以对于库的建设以及排查是十分重要的。(重要:需要有排查的思路)
如:将库的路径导入了环境变量中 LD_LIBRARY_PATH 中。还是不行,一定是环境变量设错了。无非就两种:
融会贯通的理解:
动态库里面的地址采用的是相对地址,所有的函数编址采用相对于库头0开始编址。正是因为如此,当其加载到内存映射时,每一个动态库,映射到进程地址空间的位置可能是不一样的。其可采用共享区中的:动态库起始加载虚拟地址 + (动态库中)函数偏移量。
所以所谓的共享区(目前可以确定):是给堆、栈足够的空间时,也用于存储动态库。
因为现实中,别人的框架、组件什么的,是好于我们自己写的,学习期间需要造轮子,但是工作上我们要学会用轮子(争取不要用自己写的东西),这是一个站在巨人肩膀上的问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。