链接概念介绍

news2024/9/24 11:32:45

链接器

为了更好地理解计算机程序的编译和链接的过程,我们简单地回顾计算机程序开发的历史一定会非常有益。计算机的程序开发并非从一开始就有着这么复杂的自动化编译、链接过程。原始的链接概念远在高级程序语言发明之前就已经存在了,在最开始的时候,程序员(当时程序员的概念应该跟现在相差很大了)先把一个程序在纸上写好,当然当时没有很高级的语言,用的都是机器语言,甚至连汇编语言都没有。当程序须要被运行时,程序员人工将他写的程序写入到存储设备上,最原始的存储设备之一就是纸带,即在纸带上打相应的孔。

这个过程我们可以通过下图来看到,假设有一种计算机,它的每条指令是1个字节,也就是8位。我们假设有一种跳转指令,它的高4位是0001,表示这是一条跳转指令;低4位存放的是跳转目的地的绝对地址。我们可以从图中看到,这个程序的第一条指令就是一条跳转指令,它的目的地址是第5条指令(注意,第5条指令的绝对地址是4)。至于0和1怎么映射到纸带上,这个应该很容易理解,比如我们可以规定纸带上每行有8个孔位,每个孔位代表一位,穿孔表示0,未穿孔表示1。

在这里插入图片描述

现在问题来了,程序并不是一写好就永远不变化的,它可能会经常被修改。比如我们在第1条指令之后、第5条指令之前插入了一条或多条指令,那么第5条指令及后面的指令的位置将会相应地往后移动,原先第一条指令的低4位的数字将需要相应地调整。在这个过程中,程序员需要人工重新计算每个子程序或跳转的目标地址。当程序修改的时候,这些位置都要重新计算,十分繁琐又耗时,并且很容易出错。这种重新计算各个目标的地址过程被叫做重定位(Relocation)

如果我们有多条纸带的程序,这些程序之间可能会有类似的跨纸带之间的跳转,这种程序经常修改导致跳转目标地址变化在程序拥有多个模块的时候更为严重。人工绑定进行指令的修正以确保所有的跳转目标地址都正确,在程序规模越来越大以后变得越来越复杂和繁琐。

没办法,这种黑暗的程序员生活是没有办法容忍的。先驱者发明了汇编语言,这相比机器语言来说是个很大的进步。汇编语言使用接近人类的各种符号和标记来帮助记忆,比如指令采用两个或三个字母的缩写,记住“jmp”比记住0001XXXX是**跳转(jump)**指令容易得多了;汇编语言还可以使用符号来标记位置,比如一个符号“divide”表示一个除法子程序的起始地址,比记住从某个位置开始的第几条指令是除法子程序方便得多。最重要的是,这种符号的方法使得人们从具体的指令地址中逐步解放出来。比如前面纸带程序中,我们把刚开始第5条指令开始的子程序命名为“foo”,那么第一条指令的汇编就是:

jmp foo

当然人们可以使用这种符号命名子程序或跳转目标以后,不管这个“foo”之前插入或减少了多少条指令导致“foo”目标地址发生了什么变化,汇编器在每次汇编程序的时候会重新计算“foo”这个符号的地址,然后把所有引用到“foo”的指令修正到这个正确的地址。整个过程不需要人工参与,对于一个有成百上千个类似的符号的程序,程序员终于摆脱了这种低级的繁琐的调整地址的工作,用一句政治口号来说叫做“极大地解放了生产力”。符号(Symbol) 这个概念随着汇编语言的普及迅速被使用,它用来表示一个地址,这个地址可能是一段子程序(后来发展成函数)的起始地址,也可以是一个变量的起始地址。

有了汇编语言以后,生产力大大提高了,随之而来的是软件的规模也开始日渐庞大。这时程序的代码量也已经开始快速地膨胀,导致人们要开始考虑将不同功能的代码以一定的方式组织起来,使得更加容易阅读和理解,以便于日后修改和重复使用。自然而然,人们开始将代码按照功能或性质划分,分别形成不同的功能模块,不同的模块之间按照层次结构或其他结构来组织。这个在现代的软件源代码组织中很常见,比如在C语言中,最小的单位是变量和函数,若干个变量和函数组成一个模块,存放在一个“.c”的源代码文件里,然后这些源代码文件按照目录结构来组织。在比较高级的语言中,如Java中,每个类是一个基本的模块,若干个类模块组成一个包(Package),若干个包组合成一个程序。

在现代软件开发过程中,软件的规模往往都很大,动辄数百万行代码,如果都放在一个模块肯定无法想象。所以现代的大型软件往往拥有成千上万个模块,这些模块之间相互依赖又相对独立。这种按照层次化及模块化存储和组织源代码有很多好处,比如代码更容易阅读、理解、重用,每个模块可以单独开发、编译、测试,改变部分代码不需要编译整个程序等。

在一个程序被分割成多个模块以后,这些模块之间最后如何组合形成一个单一的程序是须解决的问题。模块之间如何组合的问题可以归结为模块之间如何通信的问题,最常见的属于静态语言的C/C++模块之间通信有两种方式,一种是模块间的函数调用,另外一种是模块间的变量访问。函数访问须知道目标函数的地址,变量访问也须知道目标变量的地址,所以这两种方式都可以归结为一种方式,那就是模块间符号的引用。模块间依靠符号来通信类似于拼图版,定义符号的模块多出一块区域,引用该符号的模块刚好少了那一块区域,两者一拼接刚好完美组合如下图。这个模块的拼接过程就是链接(Linking)

在这里插入图片描述

这种基于符号的模块化的一个直接结果是链接过程在整个程序开发中变得十分重要和突出。链接器将这些编译后的模块链接到一起,最终产生一个可以执行的程序。

静态链接

程序设计的模块化是人们一直在追求的目标,因为当一个系统十分复杂的时候,我们不得不将一个复杂的系统逐步分割成小的系统以达到各个突破的目的。一个复杂的软件也如此,人们把每个源代码模块独立地编译,然后按照须要将它们“组装”起来,这个组装模块的过程就是链接(Linking)链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。链接器所要做的工作其实跟前面所描述的“程序员人工调整地址”本质上没什么两样,只不过现代的高级语言的诸多特性和功能,使得编译器、链接器更为复杂,功能更为强大,但从原理上来讲,它的工作无非就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了地址和空间分配(Address and StorageAllocation)、符号决议(Symbol Resolution)和重定位(Relocation)等这些步骤

符号决议有时候也被叫做符号绑定( Symbol Binding )名称绑定(Name Binding )名称决议(Name Resolution ),甚至还有叫做地址绑定(Address Binding )指令绑定( lnstruction Binding ) 的,大体上它们的意思都一样,但从细节角度来区分,它们之间还是存在一定区别的,比如“决议”更倾向于静态链接,而“绑定”更倾向于动态链接,即它们所使用的范围不一样。在静态链接,我们将统一称为符号决议。

最基本的静态链接过程如图所示。每个模块的源代码文件(如,c)文件经过编译器编译成目标文件(Object File,一般扩展名为.o或.obj),目标文件和库(Library) 一起链接形成最终可执行文件。而最常见的库就是运行时库(Runtime Library),它是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。

在这里插入图片描述

我们认为对于Object文件没有一个很合适的中文名称,把它叫做中间目标文件比较合适,简称为目标文件,后面的内容都将称Object文件为目标文件,很多时候我们也把目标文件称为模块。

现代的编译和链接过程也并非想象中的那么复杂,它还是一个比较容易理解的概念。比如 我们在程序模块main.c中使用另外一个模块func.c中的函数foo()。我们在main.c模块中每一处调用foo的时候都必须确切知道foo这个函数的地址,但是由于每个模块都是单独编译的,在编译器编译main.c的时候它并不知道foo函数的地址,所以它暂时把这些调用foo的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址修正。如果没有链接器,须要我们手工把每个调用foo的指令进行修正,则填入正确的foo函数地址。当func.c模块被重新编译,foo函数的地址有可能改变时,那么我们在main.c中所有使用到foo的地址的指令将要全部重新调整。这些繁琐的工作将成为程序员的噩梦。使用链接器,你可以直接引用其他模块的函数和全局变量而无须知道它们的地址,因为链接器在链接的时候,会根据你所引用的符号foo,自动去相应的func.c模块查找foo的地址,然后将main.c模块中所有引用到 foo的指令重新修正,让它们的目标地址为真正的foo函数的地址。这就是静态链接的最基本的过程和作用

在链接过程中,对其他定义在目标文件中的函数调用的指令须要被重新调整,对使用其他定义在其他目标文件的变量来说,也存在同样的问题。让我们结合具体的CPU指令来了解这个过程。假设我们有个全局变量叫做var,它在目标文件A里面。我们在目标文件B里面要访问这个全局变量,比如我们在目标文件B里面有这么一条指令:

movl $0x2a,var

这条指令就是给这个var变量赋值0x2a,相当于C语言里面的语句var=42。然后我们编译目标文件B,得到这条指令机器码,如下图所示:

在这里插入图片描述

由于在编译目标文件B的时候,编译器并不知道变量var的目标地址,所以编译器在没法确定地址的情况下,将这条mov指令的目标地址置为0,等待链接器在将目标文件A和B链接起来的时候再将其修正。我们假设A和B链接后,变量var的地址确定下来为0x1000,那么链接器将会把这个指令的目标地址部分修改成0x1000。这个地址修正的过程也被叫做重定位(Relocation),每个要被修正的地方叫一个重定位入口(Relocation Entry)。重定位所做的就是给程序中每个这样的绝对地址引用的位置“打补丁”,使它们指向正确的地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/67065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu - 搭建samba服务器

安装samba程序 使用如下命令安装samba sudo apt-get install samba sudo apt-get install smbclient验证是否安装成功,查看samba版本 samba -V配置samba服务器 samba的配置文件所在位置为:/etc/samba/smb.conf,使用vim命令修改配置 sudo…

[激光原理与应用-40]:《光电检测技术-7》- 常见光干涉仪及其应用

目录 第1章 干涉仪概述 1.1 什么是干涉仪 1.2 基本原理 1.3 分类 1.4 应用 1.5 干涉仪的类型 第2章 常见光干涉仪 2.1 迈克尔逊干涉仪 2.2 泰曼-格林干涉仪 2.3 移相干涉测量仪 2.4 菲索共路干涉仪 第1章 干涉仪概述 1.1 什么是干涉仪 干涉仪是很广泛的一类实验技…

Vue3中 子组件v-model绑定props接收到的父组件值报update:modelValue错

开发过程中二次封装了一个搜索的组件,子组件内使用了el-select和el-input 参数分别对应父组件传入的selectValue和selectText参数 子组件内部change和input事件来同步触发组件中数据的修改 最终本地开发环境一切正常,部署到测试环境和生产环境后出现下…

java测试示例-生成ULID

ULID全称Universally Unique Lexicographically Sortable Identifier,直译就是通用唯一按字典排序的标识符,原始仓库是https://github.com/ulid/javascript,由前端开发者alizain发起,基于JavaScript语言。从项目中的commit历史来看…

基于java(ssm)留学生交流互动论坛系统源码(java毕业设计)

基于java(ssm)留学生交流互动论坛系统 留学生交流互动论坛系统,是基于java编程语言,mysql数据库,ssm框架和idea工具开发,本系统主要分为留学生,管理员两个角色,其中留学生可以注册登…

Vue中的过滤器(管道)

过滤器:将指定的数据,按照一套流程过滤加工,最后返回一个过滤之后的值 注册局部过滤器 将过滤器写在filters配置项中的是局部过滤器,只供该vue匹配的容器使用 new Vue({el: #root,data: function(){return {time: 1670297916166}}…

JVM之内存区域划分、类加载和垃圾回收

文章目录前言一、JVM内存区域划分二、类加载1.类加载是什么?2.类加载的过程3.何时触发类加载?4.双亲委派模型三、垃圾回收(GC)1.GC是什么?2.GC回收哪部分内容?3.怎么回收?(1&#xf…

Rust 跑简单的例子

Rust 一门赋予每个人构建可靠且高效软件能力的语言 安装 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 提示失败 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh info: downloading installer curl: (60) SSL certificate problem: certifi…

FastDFS搭建及整合Nginx实现文件上传

一、准备环境 FastDFS需要两个服务,一个tracker跟踪器,一个storage存储节点,tracker做调度配置,storage完成文件存储上传等功能。 这里我们使用两台虚拟机服务器(centos 7)来部署,有条件的同学建议直接上云…

Vue中多条件图片路径通过Map存储获取避免嵌套if-else

场景 若依前后端分离版手把手教你本地搭建环境并运行项目: 若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_前后端分离项目本地运行 前端接收到后台数据之后需进行多个条件判断进而显示对应的图片路径。 比如先判断车辆的类型、…

第十三章:AQS

AQS 基础概念为什么 AQS 是 JUC 最重要的基石?AQS 能干什么AQS内部结构AQS内部类NodeAQS 源码分析以 lock方法为入口讲解nonfairTryAcquire 方法addWaiter方法线程B线程CacquireQueued 方法B节点C节点unlockcancelAcquire 方法总结AQS 基础概念 AQS 全称&#xff1…

【树莓派】了解wiringPi库、控制继电器

目录一、wiringPi库二、继电器1、继电器介绍及接线说明2、树莓派控制继电器一、wiringPi库 wiringPi是一个很棒的树莓派IO控制库,使用C语言开发,提供了丰富的接口:GPIO控制,中断,多线程等。 在树莓派命令行输入gpio -…

供应商管理软件有哪些特点和优势?

在这个快节奏的商业环境中,企业常常需要同时处理多个供应商。手动处理所有这些流程会有不少困难,为了克服这个问题,供应商管理软件是市场上可用的最佳解决方案。 好用的供应商管理软件,比如广受客户好评的8Manage SRM&#xff0c…

Spring 长事务导致connection closed,又熬了一个大夜!

大家好,我是不才陈某~ 是的,今早一到公司就收到了机器人的告警,从异常日志来看是数据库连接已关闭,然后我在解决这个问题的过程中发现了几个问题,不急,听我一一道来 异常被try后没有继续抛出,导…

CN_广域网WAN@PPP协议

文章目录WAN和LANPPP协议PPP协议有三个组成部分:LCPNCP成帧方法PPP帧的格式信息部分范围工作过程PPP协议特点透明传输WAN&InternetWAN和LAN WAN:广域网(全写为 wide area network) 广 域 网局 域 网覆盖范围很广,通常跨区域较小,通常在一个区域内连…

Ubuntu内核OverlayFS权限逃逸漏洞(CVE-2021-3493)

文章目录前言关于linux kernel一、漏洞介绍二、漏洞原理三、漏洞影响版本四、漏洞复现五、修复方法前言 关于linux kernel Linux Kernel 一般指Linux内核。Linux是一种开源电脑操作系统内核。它是一个用C语言写成,符合POSIX标准的类Unix操作系统。 一、漏洞介绍 …

如何掌握HEC-RAS建模方法与涉河建设项目防洪评价报告编制

随着社会经济的快速发展,我国河道周边土地开发利用率不断增大,临河建筑物与日俱增,部分河道侵占严重,导致防洪压力增大。迫切需要对全国从事防洪评价咨询类的技术人员开展防洪评价技术方面的学习,为了让相关工程技术人…

深度学习-支持向量机(SVM)

1. 简介 在机器学习领域,支持向量机SVM(Support Vector Machine)是一个有监督的学习模型,通常用来进行模式识别、分类(异常值检测)以及回归分析。SVM算法中,我们将数据绘制在n维空间中(n代表数据的特征数),…

C++ 函数指针探幽

首先看下面两个声明代表什么意思? double* (*(*pf)[2])(double*,int); double* (*pa[2])(double*,int);要搞清楚这两个式子,则先要清楚 指向指针的指针指针数组与指向数组的指针函数指针 指向指针的指针 指针的指针特殊点在于指向的是一个指针而已&am…

栈与队列2:用队列实现栈

主要是我自己刷题的一些记录过程。如果有错可以指出哦,大家一起进步。 转载代码随想录 原文链接: 代码随想录 leetcode链接:344. 反转字符串 题目: 请你仅使用两个队列实现一个后入先出(LIFO)的栈&#x…