Linux学习二(GCC/静态库动态库)
Linux学习二 : GCC/静态库动态库
GCC
GCC 是 Linux 下的编译工具集,是 GNU Compiler Collection 的缩写,包含 gcc、g++ 等编译器。
代码变成可执行程序的四步:预编译,编译,汇编,链接。
假设已经编写好name.c源文件。
预编译:主要进行三件事:展开头文件,宏替换,去掉注释行。得到.i文件。使用到的参数为-E
gcc -E name.c -o name.i
编译:编译器的对文件进行编译,得到汇编文件。得到.s文件。使用到的参数为-S
gcc -S name.i -o name.s
汇编:对汇编文件进行进行汇编,会生成相应的二进制目标文件,得到.o文件。使用到的参数为 -c
gcc -c name.s -o name.o
链接:调用链接器对程序需要调用的库进行链接, 最终得到一个可执行的二进制文件。还是.o文件。不需要参数
gcc name.o -o name.exe
一步完成:gcc name.c -o name.exe-o 是为了指定生成的文件名
-I 可以重新指定头文件路径
gcc name.c -o name.exe -I/testhead #将头文件路径指定到/testhead下
多文件编译
gcc -o name name.c main.c
GCC和G++的主要区别
- 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译,g++则统一当做cpp文件编译
- 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL
- gcc在编译C文件时,可使用的预定义宏是比较少的
静态库和动态库
使用库:一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密。当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的API函数的声明,也就是头文件,把这些都添加到项目中。
静态库
静态库:在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。(这里的移植方便指的是.exe移植方便)
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
创建静态库的过程:Linux下使用ar工具、Windows下vs使用lib.exe,将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索。生成静态库:
先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar工具将目标文件打包就可以得到静态库文件。
- 创建需要链接的头文件和.cpp文件。
- 对.cpp文件进行汇编,得到.o文件。注意带参数-c,否则直接编译为可执行文件。
- 通过ar工具将目标文件打包成.a静态库文件:Linux静态库命名规范,必须是”lib[your_library_name].a”:lib为前缀,中间是静态库名,扩展名为.a。
ar工具的三个参数
- c:创建一个库
- s:创建目标索引,能加快库的创建事件
- r:在库中插入模块(替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。
- 使用静态库:将.a库文件和对应的头文件给使用者,得到静态库进行调用的时候,调用程序编译的时候:
-L: 指定库所在的目录(相对或者绝对路径)
-l: (小写L) 指定库的名字, 掐头(lib)去尾(.a)
动态库
静态库已经可以代码复用,为什么还要用动态库?
- 空间浪费,会存在多份拷贝,如果有多个程序需要调用该链接文件的话。
- 静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户,可能是一个很小的改动,却导致整个程序重新下载,全部更新。
动态库:
- 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入。
- 不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。可以实现进程之间的资源共享。
- 动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
Window与Linux执行文件格式不同,在创建动态库的时候有一些差异。
- 在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数做出初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。
- Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。
与创建静态库不同的是,不需要打包工具(ar、lib.exe),直接使用编译器即可创建动态库。
动态库的命名规则:
在Linux中动态库以lib作为前缀, 以.so作为后缀, 中间是库的名字自己指定即可, 即: libxxx.so
在Windows中动态库一般以lib作为前缀, 以dll作为后缀, 中间是库的名字需要自己指定, 即: libxxx.dll
生成动态库:
- 生成目标文件,此时要加编译器选项-fpic,使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
- 生成动态库,此时要加链接器选项-shared,告诉编译器生成一个动态链接库。两步可以合为一步。
g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp- 使用库的时候,在编译的时候需要指定库相关的信息: 库的路径 -L和 库的名字 -l(小写L)。
- 直接使用无法加载动态库:需要解决动态库无法加载问题。
静态库的加载:链接阶段,提供的静态库会被打包到可执行程序中。当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。
动态库的加载:
- 虽然指定了库路径(使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
- 同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
- 可执行程序被执行起来之后:
- 程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息
- 当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载
- 动态库的检测和内存加载操作都是由动态链接器来完成的。
动态链接器检索动态库的顺序:
- 可执行文件内部的 DT_RPATH 段
- 系统的环境变量 LD_LIBRARY_PATH
- 系统动态库的缓存文件 /etc/ld.so.cache
- 存储动态库/静态库的系统目录 /lib/, /usr/lib等
为了让动态链接器找到动态库:三个方法
方案1: 将库路径添加到环境变量 LD_LIBRARY_PATH 中,找到相关的配置文件
- 用户级别: ~/.bashrc —> 设置对当前用户有效
- 系统级别: /etc/profile —> 设置对所有用户有效
添加配置路径:
1 |
|
方案2: 更新 /etc/ld.so.cache 文件
- 找到动态库所在的绝对路径(不包括库的名字)比如:/home/robin/Library/。
- 修改 /etc/ld.so.conf 这个文件,添加上面的语句。
- 更新 /etc/ld.so.conf中的数据到 /etc/ld.so.cache 中
1 |
|
方案3: 拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (或者将库的软链接文件放进去)
1 |
|
动态库与静态库的优缺点:
静态库:
* 静态库被打包到应用程序中加载速度快。
* 发布程序无需提供静态库,移植方便。
* 相同的库文件数据可能在内存中被加载多份, 消耗系统资源,浪费内存。
* 库文件更新需要重新编译项目文件, 生成新的可执行程序, 浪费时间。
动态库:
- 可实现不同进程间的资源共享
- 动态库升级简单, 只需要替换库文件, 无需重新编译应用程序。
- 程序猿可以控制何时加载动态库, 不调用库函数动态库不会被加载。
- 加载速度比静态库慢, 以现在计算机的性能可以忽略。
- 发布程序需要提供依赖的动态库。
参考列表: