计算机程序的编译和链接

9efbcbc3d25747719da38c01b3fa9b4f.gif

 c语言中的小小白-CSDN博客c语言中的小小白关注算法,c++,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm=1001.2014.3001.5343

给大家分享一句我很喜欢我话:

知不足而奋进,望远山而前行!!!

铁铁们,成功的路上必然是孤独且艰难的,但是我们不可以放弃,远山就在前方,但我们能力仍然不足,所有我们更要奋进前行!!!

今天我们更新了编译和链接内容,

🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

前言:

在日常的应用程序开发过程中,我们很少需要关注软件的编译和连接过程,特别是对于常用的集成开发环境visual studio,它将编译和链接的过程封装起来,一步完成,称为“构建”。
     但是在这样的开发过程中,我们往往依赖于集成开发环境的强大,而忽略了软件的运行机制和机理,导致对程序中的很多莫名其妙的错误无从下手,程序运行时的性能瓶颈分析也让我们束手无策,如果我们能够深入了解软件运行背后的机理以及支撑软件运行的各种平台和工具,那么解决这些问题相对来说就比较容易了。接下来让我们一起了解软件编译与链接的过程。

一、预处理

  预处理过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如”#include”,”#define”等,主要处理规则如下:
(1)将所有的”#define”删除,并且展开所有的宏定义。因为宏定义是直接展开的,所以我们在定义运算符相关宏时,切记要带上括号,避免导致歧义。
(2)处理所有的条件预编译指令,比如“#if“,”#ifdef“,”#elif”,“#else”,”#endif”
(3)处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置,注意:这个过程时递归进行的,也就是说被包含的文件可能还包含其它文件。当程序项目较大时,由于头文件包含较多,会导致编译速度减慢,此时可以从头文件的包含着手解决,避免包含无用的头文件,以及重复包含问题。
(4)过滤所有的注释“//“和”/**/“中的内容
(5)添加行号和文件名标识,比如#2“hello.c“ 2,以便于编译时编译器产生调试用的行号信息,及用于编译时产生的编译错误和编译警告时显示行号。
(6)保留所有的#pragma编译指令,程序编译时编译器需要使用到。
(7)处理预定义宏,在不同的编译器中,会有一些常用的预定义宏,比如__FILE__,__FUNCTION__,__LINE__。
(8)预处理不做任何语法检查,不仅是因为它不具备语法检查功能,也因
为预处理命令不属于 C/C++ 语句(这也是定义宏时不要加分号的原因),语法
检查是编译器要做的事情。

     通过以下命令可以对源文件进行预编译操作,编译后的文件扩展名是.ii。

二、编译

编译过程就是把与预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。可以通过以下命令进行编译(注意大写S):

gcc –S hello.i –o hello.s

  经过编译后的.s文件中是汇编代码,可以直接打开查看其内容:

究竟编译器做了什么?从最直观的角度来讲,编译器就是将高级语言翻译成机器语言的一个工具。比如用C/C++语言写的一个程序,可以通过编译器将其翻译成计算机可以执行的指令以及数据,编译的过程一般分为六步:扫描(词法分析),语法分析,语义分析,源代码优化,代码生成和目标代码优化。整个过程如图所示:

2.1词法分析: 

 array[index] = (index + 5) * (2 + 7);
 

  词法分析产生的记号一般可以分为以下几类:关键字,常数,运算符,标识符。在识别记号的同时,扫描器也完成了其它工作:比如将标识符存放到符号表,将数字和字符串常量存放到文字表等,以备后续步骤使用。
     对于C/C++语言,走到词法分析这一步时,宏替换以及文件包含已经在预处理中处理完毕。

2.2语法分析:

 在语法分析的同时,很多运算符号的优先级和含义也被确定下来了。比如乘法表达式比加法表达式的优先级高。另外有些符号具有多重含义,比如 * 在C语言中可以表示乘法表达式,也可以表示指针取内容的表达式,所以语法分析阶段必须对这些内容进行区分。如果出现了表达式不合法,比如各种括号不匹配、表达式中缺少操作符等,编译器就会报告语法分析阶段的错误

2.3语义分析:

 语义分析是由语义分析器来对表示的语法层面进行的分析,但是它并不了解这个语句是否真正有意义。比如C/C++中对两个指针做乘法运算是没有意义的,但是这个语句在语法上是合法的;比如同样一个指针和浮点数做乘法运算是否合法等。编译器所能分析的语义是静态语义,也就是编译期可以确定地语义,与之对应地动态语义就是只有在运行期才能确定的语义。
     静态语义通常包括声明和类型的匹配,类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式,这其中包含了一个浮点型到整型的准换过程,语义分析就负责完成这个步骤。比如将浮点数赋值给指针时,语义分析会发现这个类型不匹配(整型时可以赋值给指针的),编译器就会报错。
     动态语义一般指在运行期出现的语义相关的问题,比如将0作为除数是一个运行期语义错误。
     经过语义分析阶段后,整个语法树的表达式都被标上了类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。上秒描述的语法树在经过语义分析阶段后变化如图所示:

2.4中间语言的生成:

现代的编译器有着很多层次的优化,往往在源代码级别会有一个优化过程。这里所描述的源码级优化器在不同编译器中可能会有不同的定义或者一些其它差异。源代码优化器会在源码级别进行优化,在上例中,我们可以发现,(2+7)这个表达式可以被优化掉,因为它的值在编译期就可以确定,优化后的语法树为:


     我们看到(2+7)这个表达式被直接优化成9。由于直接在语法树上进行优化比较困难,因此源代码优化器往往先将整个语法树转换成中间代码,它是语法树的顺序表示,已经非常接近目标代码了。但是中间代码一般跟目标机器和运行时的环境是无关的,比如不包含数据的大小,变量的地址和寄存器的名称等等。中间代码在不同的编译器中有着不同的形式,此处不再详细介绍。
     中间代码使得编译器可以被分为前端和后端:前端负责产生机器无关的中间代码,后端负责将中间代码转换成目标机器代码。这样对于一些跨平台的编译器而言,它们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端。

2.5目标代码的优化:

 源代码级优化器产生中间代码标志着下面的过程都是由编译器后端来完成的:代码生成器和目标代码优化器。
     代码生成器将中间代码转换为与机器相关的目标机器代码,这个过程依赖于目标机器的结构,因为不同机器的字长,寄存器,整数数据类型,浮点数数据类型都不一样(很简单的例子:32位操作系统和64位操作系统,指针变量所占字节数分别为4字节和8字节)。比如常用的GCC编译器就几乎支持所有的CPU平台,当然这也导致它的指令生成过程更为复杂。
     最后目标代码优化器对转换后的目标代码进行优化,比如选择合适的寻址方式,位移来代替乘法运算,删除多余的指令等。
     经过了词法分析,语法分析,语义分析,源代码优化,目标代码生成和目标代码优化,编译器经过这么多步骤,终于将源代码编译成目标代码。但是上述目标代码中index和array的地址还没有确定,如果现在把目标代码使用汇编器编译成真正能够在机器上执行的指令,那么index和array的地址是从哪里来的呢,如果它们定义跟上述源码在同一个编译单元内,那么编译器可以为它们分配空间,确定地址,但是如果index和array是定义在其它的程序模块中呢?
     事实上,定义其它模块的全局变量和函数在最终运行时的绝对地址都要在最终链接时才能确定。所以现代的编译器可以将一个源代码文件编译成一个未链接的目标文件(比如gcc中通过gcc –c hello.c 会生成hello.o文件),然后由连接器将这些目标文件链接起来形成最终的可执行文件(gcc hello.o)。

三、链接

把每个源代码模块独立地编译,然后按照需要将它们进行“组装”,这个组装地过程就是链接。链接地主要内容就是把各个模块之间相互引用地部分(包括函数和变量)都处理好,使得各个模块之间能够正确地衔接。
     从原理上讲,链接地工作无非就是把一些指令对其他符号地址地引用加以修饰,链接主要包括了地址和空间分配,符号决议和重定位这些步骤,
     举一个简单的例子:比如我们在模块main.c中使用另一个模块func.c中的函数foo(),我们在main.c模块中每一处调用foo函数的时候都必须确切知道foo的函数地址,但是由于每个模块都是单独编译的,在编译器编译main.c的时候它并不知道foo函数的地址(但是由于编译的预处理阶段,是将头文件全部替换的,因此编译单独编译main模块是没有问题的),所以暂时把这些调用foo的指令的目标地址搁置,等待最后链接的时候由连接器去将这些指令的目标地址进行修正。

Mov1 $0x2a, var

 这条指令就是给这个var变量赋值0x2a,由于比在编译目标文件B的时候,编译器并不知道变量var的目标地址,所以在这种情况下,编译器将这条mov指令的目标地址设为0,等待链接器在将目标文件A和B链接起来的时候再将其修正。假设A和B链接后,变量var的地址确定下来为0x100,那么链接起会把这个指令的目标地址修改成0x100。这个地址修正的过程叫做重定位,每个要被修正的地方叫一个重定位入口(在编译过程中报错,找不到函数的入口,那就是因为在链接时找不到该函数的地址)。

四、总结:

本期我们讲了关于计算机程序的编译与链接,希望对大家有所帮助!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录第20天| 654.最大二叉树 617.合并二叉树

654.最大二叉树 654. 最大二叉树 - 力扣(LeetCode) 代码随想录 (programmercarl.com) 又是构造二叉树,又有很多坑!| LeetCode:654.最大二叉树_哔哩哔哩_bilibili 给定一个不重复的整数数组 nums 。 最大二叉树 可以…

python+selenium做ui自动化测试用法必会

一、前言 大家都知道,基于Web端的测试的基础框架是需要Selenium做主要支撑的,这里边给大家介绍下Web测试核心之基于Python的Selenium Selenium是用于测试Web应用程序用户界面(UI)的常用框架。它是一款用于运行端到端功能测试的超强工具。您可以使用多个编…

ExperimentalWarning: The http2 module is an experimental API.

错误提示 Node.js:ExperimentalWarning: The fs.promises API is experimental原因是node的版本不是最新的,而在项目引入的模块是最新的,node.js的版本低于模块的版本: 解决方法: 1、升级版本 npm install -g npm 更新npm到最新版 npm ins…

MyBatis3源码深度解析(二十一)动态SQL实现原理(二)动态SQL解析过程、#{}和${}的区别

文章目录 前言8.5 动态SQL解析过程8.5.1 SQL配置转换为SqlSource对象8.5.2 SqlSource转换为静态SQL语句 8.6 #{}和${}的区别8.7 小结 前言 在【MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件】中研究了MyBatis动态SQL相关的组件,如SqlSource用于…

vue.js制作学习计划表案例

通俗易懂,完成“学习计划表”用于对学习计划进行管理,包括对学习计划进行添加、删除、修改等操作。 一. 初始页面效果展示 二.添加学习计划页面效果展示 三.修改学习计划完成状态的页面效果展示 四.删除学习计划 当学习计划处于“已完成”状态时&…

Linux基础-Makefile

目录 一、Make简介 二、Makefile基本结构 示例: 补充(Makefile): 伪目标: 三、创建和使用变量 变量定义的方式: 简单方式: 递归方式: 用?定义变量 为变量添加值 预定义变量 例 自动变量 例…

C++函数模板详解(结合代码)

目录 1. 模板概念 2. 函数模板语法 3. 函数模板注意事项 4. 函数模板案例 5. 普通函数与函数模板的区别 6. 普通函数与函数模板的调用规则 7. 模板的局限性 1. 模板概念 在C中,模板是一种通用的程序设计工具,它允许我们处理多种数据类型而不是固…

【STM32嵌入式系统设计与开发】——9Timer(定时器中断实验)

这里写目录标题 一、任务描述二、任务实施1、ActiveBeep工程文件夹创建2、函数编辑(1)主函数编辑(2)USART1初始化函数(usart1_init())(3)USART数据发送函数( USART1_Send_Data(&…

【Java基础知识总结 | 第六篇】Java反射知识总结

文章目录 6.Java反射知识总结6.1概述6.1.1什么是反射?6.1.2为什么使用反射? 6.2反射的原理6.3反射的使用6.3.1获取类对象(1)通过具体类的类名获取(2)通过对象实例获取(3)通过class.f…

正式发布:VitePress 1.0 现代化静态站点生成器!

大家好,我是奇兵,今天介绍一下现代化静态站点生成器!,希望能帮到大家。 3 月 21 日, 由 Vue 团队出品的现代化静态站点生成器 VitePress 正式发布 1.0 版本!它专为构建快速、以内容为中心的网站而生,能够轻…

Django之Celery篇(一)

一、介绍 Celery是由Python开发、简单、灵活、可靠的分布式任务队列,是一个处理异步任务的框架,其本质是生产者消费者模型,生产者发送任务到消息队列,消费者负责处理任务。 Celery侧重于实时操作,但对调度支持也很好,其每天可以处理数以百万计的任务。特点: 简单:熟悉…

获取Book里所有sheet的名字,且带上超链接

应用背景: 当一个excel有很多sheet的时候,来回切换sheet会比较复杂,所以我希望excel的第一页有目录,可以随着sheet的增加,减少,改名而随时可以去更新,还希望有超链接可以直接跳到该sheet。 可以…

VPCFormer:一个基于transformer的多视角指静脉识别模型和一个新基准

文章目录 VPCFormer:一个基于transformer的多视角指静脉识别模型和一个新基准总结摘要介绍相关工作单视角指静脉识别多视角指静脉识别Transformer 数据库基本信息 方法总体结构静脉掩膜生成VPC编码器视角内相关性的提取视角间相关关系提取输出融合IFFN近邻感知模块(NPM) patch嵌…

ssm006基于java的少儿编程网上报名系统+vue

少儿编程网上报名系统 摘 要 在国家重视教育影响下,教育部门的密确配合下,对教育进行改革、多样性、质量等等的要求,使教育系统的管理和运营比过去十年前更加理性化。依照这一现实为基础,设计一个快捷而又方便的网上少儿编程网上…

pdf压缩文件怎么压缩最小?一键压缩PDF

pdf文件压缩是为了减小文件大小,以便更轻松地共享、传输和存储文件,通过压缩pdf文件,可以减少文件占用的存储空间,加快文件的上传和下载速度,并节省带宽和存储成本;在本教程中,我们将介绍一些有效的方法来最…

如何自定义一个starter?

在Spring Boot中,创建一个自定义starter可以简化特定功能或组件的配置过程,让其他项目能够轻松地重用这些功能。 一、问题解析 这里我们以自定义一个xxl-job的starter为例,介绍下如何简化配置。 添加依赖 添加Spring Boot的依赖&#xff1a…

关于网格数据导出指定格式的测试(以Gmsh导出nas格式为例)

本文主要讲述Gmsh如何导出nas格式的网格数据,众所周知,Gmsh可以导出多种网格数据格式,比如大家熟悉的msh、stl、inp、cgns(似乎不完善)等等,但是gmsh不支持nas格式的导出,只支持nas格式的导入&a…

Bug定位与分析,软件测试员你中招了吗?

之所以写这一篇文章,是突然想起来曾经在测试过程中被开发嘲讽过,事情是这样的,当时发现了一个疑似前端的Bug就草草提交到了禅道,结果刚来的女前端看到了就有点生气地问我为啥不查清到底是前后端问题就直接派给她前端了&#xff0c…

睿考网:不是会计专业能考中级会计师吗?

不是会计专业也是可以考中级会计师的,中级会计师报名条件中并没有对专业做明确的限制,不同的学历对工作年限的要求不一样,如果考生满足报考条件就可以参加。 1.具备大学专科学历,从事会计工作满5年。 2.具备大学本科学历或学士学…