C/C++链接过程、动态链接库.so和静态链接库.a

前言

纯个人学习总结

  • gcc:全称是GNU Compiler Collection,它是一个能够编译多种语言的编译器。
  • g++:GCC把后缀为c的文件当C代码处理(ccl编译),而 G++ 则当作 C++ 处理(cclplus编译)GCC默认是不能编译 C++ 程序的,需要加上-lstdc++选项,G++ 是可以直接编译的,G++相当于是对GCC的封装。
  • 符号:Symbol,它是随着汇编语言普及被使用的,它用来表示一个地址,这个地址可以是一段函数,也可以是一个变量的起始地址。
  • 符号表:存放两种符号,一个是当前目标文件可以提供给其它目标文件使用的符号,另一个其它目标文件需要提供给当前目标文件使用的符号。

源码编译链接过程

比如一个最简单的hello.c

#include <stdio.h>
int main()
{
    printf("Hello World!\n");
    return 0;
}

1.预编译(预处理)
主要处理源码中#开头的预编译指令,比如#include#define等。
这一步预编译器cpp将hello.c和stdio.h头文件预编译成.i文件。
gcc -E hello.c -o hello.i
2.编译
将预处理后的文件进行一系列的优化后生成相应的汇编代码文件。
gcc -S hello.i -o hello.s
现在预编译和编译可以合成一个步骤,对于c语音使用的是cll,对于c++,对应的是cclplus。
3.汇编
使用汇编器(as)将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。
gcc -c hello.s -o hello.o
4.链接
ld命令,最终生成可执行文件hello,默认为a.out
gcc hello.o -o hello 这里没有输入参数,则表示链接。
在linux上,可以使用命令ldd hello查看链接了哪些库。
mac可以使用otool -L hello

符号表

符号表存放在目标文件(包含数据段,代码段和符号表)中。
本质上整个符号表只是想表达两件事:

  • 我能提供给其它文件使用的符号
  • 我需要其它文件提供给我使用的符号

strip

一个elf文件里有两张符号表,一张叫符号表(.symtab),另一张叫动态符号表(.dynsym),当去符号的时候,只用去掉符号表,保留动态符号表即可。
所以使用strip hello,去除的便是符号表。

strip命令不仅可以对可执行文件操作,也可以针对目标文件和动态库。
strip前的库用来调试, strip后的库用来实际发布, 他们两者有对应关系。 一旦发布的strip后的库出了问题, 就可以找对应的未strip的库来定位。

而对于目标文件和静态库,不能strip的太彻底,可能会链接失败。
可以给下边两个参数:

-g -S -d --strip-debug: Remove debugging symbols only;
--strip-unneeded: Remove all symbols that are not needed for relocation processing。

查看符号表

1.使用objdump命令。

objdump -tT xxx.so  # t:符号表  T:动态符号表

2.使用nm命令(个人觉得使用nm方式查看更方便。)

nm -D xxx.so
# -D或-dynamic选项表示:显示动态符号。该选项仅对于动态库有意义)

得到的结果中以T开头的就是导出函数。

nm列出的符号有很多,常见的有三种,一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;一种是库中定义的函数,用T表示,这是最常见的;另外一种是所谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。

具体各个类型可以参考:https://blog.csdn.net/wvtear/article/details/50016285

或者:

readelf -s xxx.so

android中可以使用NDK下的工具:
目录在NDK/android-ndk-r21b/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin下。

也可以根据符号,反过来解析出函数或者变量:

./aarch64-linux-android-c++filt 符号名称

查看so依赖的其他库:

./aarch64-linux-android-readelf -dW xxx.so | grep NEEDED

链接

链接过程主要包括地址和空间分配,符号决议和重定位等步骤。

符号决议

链接器会依次扫描每一个给定的目标文件,同时链接器还维护了两个集合,一个是已定义符号集合D,另一个是未定义符合集合U,下面是链接器进行符合决议的过程:
1,对于当前目标文件,查找其符号表,并将已定义的符号并添加到已定义符号集合D中。
2,对于当前目标文件,查找其符号表,将每一个当前目标文件引用的符号与已定义符号集合D进行对比,如果该符号不在集合D中则将其添加到未定义符合集合U中。
3,当所有文件都扫描完成后,如果未定义符号集合U不为空,则说明当前输入的目标文件集合中有未定义错误,链接器报错,整个编译过程终止。

重定位

对于重定位举个简单的例子:
模块a需要引用模块b中的函数foo(),由于模块都是单独编译的,在编译器编译模块a的时候,并不知道foo()的地址,所以它暂时将调用foo()的目标地址搁置,等链接的时候由链接器去将这些指令的目标地址修正。链接的时候会根据引用的符号foo,去模块b中查找foo的地址,然后将模块a中引用foo的指令的目标地址修正为真正的foo函数的地址。这个地址修正的过程就叫做重定位。

静态链接库

简单来说,静态库就是多个目标文件的打包集合,我们引用一个静态库,只需进行编译链接过程即可,静态库的代码会直接copy到最终的可执行文件中

对于静态链接,最终生成的可执行文件:

  • 可执行文件和目标文件一样,也是由代码段和数据段组成。
  • 每个目标文件中的数据段都合并到了可执行文件的数据段,每个目标文件当中的代码段都合并到了可执行文件的代码段。
  • 目标文件当中的符号表并没有合并到可执行文件当中,因为可执行文件不需要这些字段。

缺点:由于将代码都copy到可执行文件中,那就会增大文件的大小。当我们有多个可执行文件都使用了同一个静态库也会造成重复冗余。

动态链接库

动态链接库的出现就解决了上边的问题。
动态库允许使用该库的可执行文件仅仅包含对动态库的引用而无需将该库拷贝到可执行文件当中。
动态链接可以在两种情况下被链接使用,分别是load-time dynamic linking(加载时动态链接) 以及 run-time dynamic linking(运行时动态链接)

  • load-time dynamic linking(加载时动态链接)
    这里的加载指的是程序的加载,而所谓程序的加载就是把可执行文件从磁盘搬到内存的过程,因为程序最终都是在内存中被执行的。操作系统会查找可执行文件依赖的动态库信息(主要是动态库的名字以及存放路径),找到该动态库后就将该动态库从磁盘搬到内存,并进行符号决议。从总体上看,加载时动态链接可以分为两个阶段:阶段一,将动态库信息写入可执行文件;阶段二,加载可执行文件时依据动态库信息进行动态链接。
    相比较与静态链接,加载时链接将过程推迟到了程序启动加载时
  • run-time dynamic linking(运行时动态链接)
    相比较与加载时,运行时将这个过程再次推迟到了程序运行时
    由于在编译链接生成可执行文件的过程中没有提供所依赖的动态库信息,因此这项任务就留给了程序员,在代码当中如果需要使用某个动态库所提供的函数,我们可以使用特定的API来运行时加载动态库,在Windows下通过LoadLibrary或者LoadLibraryEx,在Linux下通过使用dlopen、dlsym、dlclose这样一组函数在运行时链接动态库。当这些API被调用后,同样是首先去找这些动态库,将其从磁盘copy到内存,然后查找程序依赖的函数是否在动态库中定义。这些过程完成后动态库中的代码就可以被正常使用了。

对于动态链接生成的可执行文件,除了数据段和代码段,会新增两段,即dynamic段以及GOT(Global offset table)段。
dynamic段中保存了可执行文件依赖哪些动态库,动态链接符号表的位置以及重定位表的位置等信息。当加载可执行文件时,操作系统根据dynamic段中的信息即可找到使用的动态库,从而完成动态链接。

动态链接库的显式调用和隐式调用

https://oldpan.me/archives/something-about-so-we-dont-know

拓展阅读

为什么对动态链接库(.so) strip 之后仍然可以对.so进行链接?
彻底理解链接器:二,符号决议
彻底理解链接器:三,库与可执行文件
彻底理解链接器:四,重定位

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的静态链接库动态链接库的生成方法略有不同。下面分别介绍: 1. 静态链接库(Static Library) 静态链接库是指在编译链接阶段将库的代码复制到可执行文件中,因此可执行文件不再依赖于库文件,可以单独运行。静态链接库的生成方法如下: - 编写库的源代码,生成库的目标文件(.o)。 - 将所有目标文件打包成静态链接库文件(.a)。 具体的步骤如下: 首先编译库的源代码,生成目标文件(.o): ``` g++ -c lib.cpp -o lib.o ``` 然后将目标文件打包成静态链接库文件(.a): ``` ar rcs lib.a lib.o ``` 其中,`ar`命令用于打包目标文件,`rcs`参数表示创建库文件并插入目标文件,`lib.a`为库文件名,`lib.o`为目标文件名。 2. 动态链接库(Dynamic Library) 动态链接库是指在程序运行时加载库文件,因此可执行文件依赖于库文件,需要和库文件一起运行。动态链接库的生成方法如下: - 编写库的源代码,生成库的目标文件(.o)。 - 将所有目标文件编译为共享对象文件(.so)。 具体的步骤如下: 首先编译库的源代码,生成目标文件(.o): ``` g++ -c -fPIC lib.cpp -o lib.o ``` 其中,`-fPIC`参数表示生成位置无关代码,是动态链接库的必备参数。 然后将目标文件编译为共享对象文件(.so): ``` g++ -shared -o lib.so lib.o ``` 其中,`-shared`参数表示生成共享对象文件,`lib.so`为共享对象文件名,`lib.o`为目标文件名。 以上就是C++中生成静态链接库动态链接库的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值