链接

注:本文内容摘自《深入理解计算机系统》

首先搞清楚什么是链接?链接是将各种代码和数据收集起来,并组合成为一个单一文件的过程。链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到存储器并执行时;甚至执行于运行时(run time),由应用程序来执行。
链接是由叫做链接器(linker)的程序自动执行的。

认识编译驱动程序

编译系统提供编译驱动程序(compiler driver),它读取程序源文件并将其翻译成一个可执行的目标文件,这个过程分成四个阶段,分别是:

1. 预处理阶段(预处理器cpp)
2. 编译阶段(编译器ccl)
3. 汇编阶段(汇编器as)
4. 链接阶段(链接器ld)

我们结合以下程序来了解一个基本的”翻译”过程。

/*main.c*/

void swap();
int buf[2]={1,2};
int main()
{
    swap();
    return 0;
}
----------
/*swap.c*/

extern int buf[];
int *bufp0=&buf[0];
int *bufp1;
void swap()
{
    int tmp;
    bufp1=&buf[1];
    tmp=*bufp0;
    *bufp0=*bufp1;
    *bufp1=tmp;
}

在这个示例程序中,main()函数调用swap()函数来交换全局数组buf中的两个数。
我们一步一步来,看看每一步到底发生了什么。

当前目录下,有main.c和swap.c两个文件

kkmjy@ubuntu:link$ ls
main.c  swap.c

step1、运行预处理器(cpp)将.c文件翻译成中间文件(即.i文件)

kkmjy@ubuntu:link$ cpp main.c main.i
kkmjy@ubuntu:link$ cpp swap.c swap.i

经过step1之后,

kkmjy@ubuntu:link$ ls
main.c  main.i  swap.c  swap.i

step2、运行编译器(ccl)将.i文件翻译成汇编语言文件(即.s文件)
(注:若此前没有安装过ccl,会跳出来这句话,按要求安装好就OK了)

The program 'ccl' is currently not installed. You can install it by typing:
sudo apt install cclive

ps:很奇怪,用ccl一直会出错,为了继续进行下去,用以下语句代替

kkmjy@ubuntu:link$ gcc -S main.i -o main.s
kkmjy@ubuntu:link$ gcc -S swap.i -o swap.s

经过step2后

kkmjy@ubuntu:link$ ls
main.c  main.i  main.s  swap.c  swap.i  swap.s

step3、运行汇编器(as)将.s文件翻译成一个可重定位目标文件(即.o文件)

kkmjy@ubuntu:link$ as main.s -o main.o
kkmjy@ubuntu:link$ as swap.s -o swap.o

经过step3后

kkmjy@ubuntu:link$ ls
main.c  main.i  main.o  main.s  swap.c  swap.i  swap.o  swap.s

step4、运行连接器(ld)将main.o和swap.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件p。

kkmjy@ubuntu:link$ ld -o p main.o swap.o

(注:用ld链接时可能会遇到cannot find entry symbol _start的问题,请移步Stack Overflow:load-warning-cannot-find-entry-symbol-start
//TODO,由于网络问题,暂时还未搞明白这个问题。

至此,我们就分步完成了”翻译”的全部工作。

以上只是为了说明”翻译”的过程而做的演示,在Linux下,用gcc编译,就一句话:

gcc -o p main.c swap.c

以上过程如下图所示:
这里写图片描述

与静态库链接

迄今为止,我们都是假设链接器读取一组可重定位目标文件,并把它们链接起来,成为一个输出的可执行文件。实际上,所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库(static library),它可以用作链接器的输入。在链接时,链接器将只拷贝被程序引用的目标模块,另一方面,应用程序员只需要包含较少的库文件的名字。在Unix系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘当中,存档文件是一组连接起来的可重定位目标文件的集合,有后缀.a标识。

下面是一个例子:
首先,我们自定义一个静态库,称它为libvector.a吧,该静态库由addvec.c和multvec.c生成。

/*addvec.c*/
void addvec(int *x,int *y,int *z,int n)
{
    int i;
    for(i=0;i<n;i++){
        z[i]=x[i]+y[i];
    }
}
------
/*multvec.c*/
void multvec(int *x,int *y,int *z,int n)
{
    int i;
    for(i=0;i<n;i++){
        z[i]=x[i]*y[i];
    }
}

我们使用ar工具来创建该库。

kkmjy@ubuntu:staticLib$ gcc -c addvec.c multvec.c //首先生成.o文件
kkmjy@ubuntu:staticLib$ ar rcs libvector.a addvec.o multvec.o //然后利用ar创建静态库

此时,便创建好了静态库libvector.a

kkmjy@ubuntu:staticLib$ ls
addvec.c  addvec.o  libvector.a  multvec.c  multvec.o

为了检测自定义的静态库是否work,写一个用例进行测试。如下:

/*vector.h*/
#ifndef VECTOR_H
#define VECTOR_H

void addvec(int *x,int *y,int *z,int n);
void multvec(int *x,int *y,int *z,int n);

#endif

----------
/*main.c*/
#include <stdio.h>
#include "vector.h"

int x[2]={1,2};
int y[2]={3,4};
int z[2]={0};

int main()
{
    addvec(x,y,z,2);
    printf("z = [%d %d]\n",z[0],z[1]);
    return 0;
}

首先,我们故意不加静态库,看看会发生什么:

kkmjy@ubuntu:staticLib$ gcc -o main main.c
/tmp/ccz5fEFs.o: In function `main':
main.c:(.text+0x19): undefined reference to `addvec'
collect2: error: ld returned 1 exit status

可以看到,出现一个”undefined reference to xxx”的错误,而这个错误就是我们经常遇到的,究其原因,就是因为链接时链接器没有找到addvec()函数对应的定义,也就是没有加上静态库。现在,让我们加上所需的静态库。如下:

kkmjy@ubuntu:staticLib$ gcc -o main main.c libvector.a

至此,程序终于编译成功了。执行结果如下:

kkmjy@ubuntu:staticLib$ ./main
z = [4 6]

总结
当链接器运行时,它判定addvec.o定义的addvec符号被main.o引用,所以它拷贝addvec.o到可执行文件。因为测试程序不引用multvec()函数,所以链接器不会拷贝这个模块到可执行文件。链接器还会拷贝libc.a中的printf.o模块,以及其他系统模块。下图概括了链接器的行为。
这里写图片描述

与动态库链接

使用静态库有以下两个缺陷:

1. 如果应用程序员想要使用一个库的最新版本,他们必须显示地将程序与更新了的库重新链接。
2. **在运行时,引用的静态库函数的代码会被复制到每个运行进程的文本段中,非常浪费存储器系统资源。**

而共享库可以解决静态库的这些缺陷。所有引用动态库的可执行目标文件,共享这个.so文件中的代码和数据,而不是像静态库那样被拷贝和嵌入到引用它们的可执行文件中。
(注:共享库(shared library),也称共享目标(shared object),在Unix系统中通常用.so后缀来表示,在Windows系统中称为dll(即动态链接库)。Anyway,它们表示的是同一个东西。)

同样用上面的例子,我们演示如何自定义动态库(libvector.so)并在程序中利用它。
首先,通过如下指令生成动态库

kkmjy@ubuntu:staticLib$ gcc -fPIC -shared -o libvector.so addvec.o multvec.o

然后,在编译main.c的加上libvector.so就好啦。

kkmjy@ubuntu:staticLib$ gcc -o p main.c libvector.so

此时,如果直接执行可执行文件p,会报错

kkmjy@ubuntu:staticLib$ ./p
./p: error while loading shared libraries: libvector.so: cannot open shared object file: No such file or directory

这是因为,链接器链接动态库时,默认的搜索路径是/usr/lib,而此时在/usr/lib目录下找不到我们自定义的libvector.so。因此,要先把libvector.so复制到/usr/lib下(注:要在root用户下)

kkmjy@ubuntu:staticLib$ sudo cp libvector.so /usr/lib

然后再执行p就没有问题啦。

以上是具体的过程。而理论部分,我们需要了解的是——可执行目标文件p在运行时可以和libvector.so链接,而这个时候,其实没有任何libvector.so的代码和数据真的被拷贝到可执行文件p中。反之,链接器拷贝了一些重定位和符号表信息,它们使得在运行时可以解析对libvector.so中代码和数据的引用。

下图概括了动态链接的过程。
这里写图片描述

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值