2.深入一点理解C源程序的编译过程

本文章的大多数灵感及知识来源于南京大学的计算机系统基础教材,如果希望更加深入地对相关知识做进一步了解,可以移步上述相关资源。在网上可以轻易获得上述资源,mooc也有相关课程。也可阅读《深入理解计算机系统》。

本文章中所有图片均来自互联网,如有侵权,劳请联系。

另外,转载请标明出处哦,语雀:https://www.yuque.com/yifeideshijie

1.直接点!一个.c文件到一个可执行文件到底经历了什么?

我们以在Linux下采用gcc进行编译为例,一个C源程序首先会经历如下几个阶段:

1.预处理

预处理程序(cpp)完成,这一阶段的任务较为简单,你只需要把以#开头的命令执行即可,正如第1篇中讲的那样,#include所包含的文件中的内容会被直接复制到文件中,#define命令所指示替换的内容也会被替换,现在,我们得到了一个.i文件,即预处理之后得到的文件,该文件与.c文件一样,依然是一个文本文件

2.编译阶段

整个过程中最烧脑最复杂的过程出现了,没错,这就是编译,整个编译阶段有编译程序(cc1)完成,得到汇编源程序(.s)文件这个过程最为复杂,还包含词法分析,语法分析,语义分析等阶段,由于这些过程很复杂,这里就不展开了,在这一过程中也是编译报错的主要来源。值得注意的是,这里的编译阶段与我们经常所说的将源程序“编译”为可执行文件中的编译是不一样的,由于整个过程中编译阶段最为复杂和重要,所以我们就直接称之为编译,实际上整个过程远非仅仅如此。

需要注意的是,编译阶段最后得到的.s系统与计算机采用的ISA(指令集体系结构)有关,这是由于每一个ISA对应其特定的汇编指令集,自然其翻译得到的汇编代码也就不一样。

ISA直接决定了物理机的设计,通常情况下,你的机器一般为x86_64/x64/amd64(三者指的是一种ISA,是由Intel的32位指令集发展而来,但是是由amd公司完成的,注意Intel公司自己开发的是IA-64,但用的不多,不是主流,商业上可称之为失败,与前述的ISA并非一物),也就是intel的机器,当然,我们可能学习的主要是IA-32/x86(两者均指的是Intel公司的32位指令集,IA指的是Intel Architecture,这个指令集由Intel公司发明,商业上获得了巨大成功,直接促成了amd64在兼容IA-32的情况下获得了成功,成为现在PC端的主流ISA)。当然还存在RISV32/64和MIPS32/64等指令集架构。

也就是说,在同一台机器上,所有的高级语言源程序最终都会翻译成相同汇编语言表示的汇编源程序,但是对于采用不同ISA的机器来说,同一个高级语言源程序最终也会翻译成不同汇编语言表示的汇编源程序。

这也就是为什么,我们在下载软件的时候,需要先明确自己的机器采用的指令集,如果你用的是主流IntelCPU机器的话,大多数情况下你可能下载的是x86-64或者amd64版本(假设你的机器是64位机)的执行程序。

3.汇编阶段

得到了.s的汇编源代码,再把相应汇编源程序通过对应的汇编程序(as)进行汇编生成可重定向目标程序(.o)文件就容易了。

这里解释为什么我们得到的文件是可重定向文件且不能直接运行的原因。

重定向是说,生成的目标文件中有一些符号的入口地址或者其对应的指向还没有明确,例如我们在main.c文件中还需要调用来自a.c文件中的help()函数,在得到的可重定向目标文件中,自然不知道help()函数的入口地址到底在哪里了。

4.链接阶段

这一阶段的目标很明确,即需要将文件中那些只是声明但没有定义的符号通过与其他的.o文件链接,从而明确这些函数或者变量的具体指向,这样,在相应的链接程序(ld)执行完相关操作后,我们就可以得到一个可执行目标程序了。

2.你需要了解一些在汇编和链接过程中的彩蛋

通过上面的阅读你可能发现了一个bug,因为你在实际中遇到了这样一种情况,同样使用IntelCPU的装了Windows的联想机器和装了Mac操作系统的苹果机器,在下载软件的时候为什么需要下载不同的文件呢?他们的指令集不是一样的吗?

这是因为,汇编程序将汇编源程序翻译为可重定向目标文件时,操作系统也在其中插了一脚,没错,生成的目标文件不仅与ISA有关,还与操作系统有关。

用户直接与操作系统打交道,给操作系统的程序必须得先让操作系统理解,才能由操作系统转交给机器运行,不同的操作系统能够理解的目标文件格式是不同的,由实现的厂家自行决定,这也就是说,同样的应用程序还要为不同的操作系统给出不同的可执行文件。Windows使用的是可移植可执行格式(COFF),DOS采用的是COM文件格式。

下面我们以现代UNIX系统采用的目标文件格式为例(如Linux),这一格式叫做可执行可链接格式,我们称之为ELF格式,由汇编程序(没错,如果操作系统理解的目标文件格式不同,自然其对应的汇编程序和链接程序都不一样)汇编得到的ELF格式的可重定位目标文件格式如下:

在该格式之下,程序中的相关数据被有组织地放在了每一个节中,不同的节描述不同类型的信息的特征,上面的图中有所描述,主要注意.text和.rodata节,这里面主要存储了相关的执行代码;以及.data和.bss节,这里主要存储全局变量数据。如若希望就此由更为深入的理解,你或许应该详细阅读南大的计算机系统基础这本书或者其他教材。

当然,这还只是可重定向的ELF文件格式,还要经过链接器得到可执行的目标文件。

首先,我们得知道链接器干了什么,无非两件事,符号解析和重定向,至于二者详细内容,那就需要详细阅读教材了,不过大体上将,符号解析就是你得知道该可重定向目标文件中的符号都指向了哪里,是指向本目标文件中定义的符号还是其他模块中定义的符号;重定向的目的是为最终的执行服务的,最终执行只要在内存上的,重定向就是为了解决最后这个目标程序到底在哪里执行,但是遗憾的是,重定向功能并非真正将程序指向一片指定的空间,而是讲其映射到虚拟存储空间。

那?什么又叫虚拟存储空间呢,虚拟的意思就是假的,并非真的,但这样的工作并非没有意义,这样的工作完成了一个很重要的工作,即这些符号的相对位置,链接器将所有的可重定向目标文件的相同的有必要留下的节合并,并通过.rel.text节和.rel.data节中的信息确定每个符号的虚拟地址。

这样,我们最终就得到了可执行目标文件(ELF文件格式)的最终的状态:

得到的这个可执行文件应该是可以直接映射到存储上的,因为操作系统要直接那这个文件到物理机器上运行了,所以,该文件中的内容是可以直接映射到存储上的,如下,我们以另一种视角看这个目标文件:

正如上图所示,执行的时候文件中一部分内容直接映射到物理地址中,对应的部分我们称之为只读段(代码段)和读写段(数据段),其余的在运行时,还包括堆区和栈区。当然,右图的只是虚拟映像,可能并非实际中的真正的存储映像,这取决于重定位类型,IA-32处理器有基本的两种重定位类型:

R_386_PC32 : 相对寻址,有效地址为PC加上重定位后地址

R_386_32: 绝对地址,即重定位后就是内存中地址

我们再加一个程序实际运行时的彩蛋:

没错,这里是很繁琐。

耐心一点吧,总会过去的。

其实这里应该是在执行的时候再详细讲的,但这里直接趁热打铁讲了吧。

在实际运行时,机器也不会就直接把目标文件中的代码段,数据段一股脑地加载到内存里,他会先耍个小聪明,先不加载,什么时候要这个数据了再去加载,当然,这里就涉及到了内存管理,即内存中发生缺页(数据没有加载到内存,但我现在要用)时,需要怎么办,这里又得扯出一大堆东西了。总之,实际运行时存在很多小技巧,具体的,可以通过操作系统或者组员或者系统结构相关书籍课程学习。

这个彩蛋它很香吗?

你或许觉得这个彩蛋讲的云里雾里,确实,它缺少很多的细节,但这里我并不想牵扯太多细节,即便如此粗略,它依然涉及了计算机系统中一个重要的思想:虚拟技术。我想表达的是,在涉及到程序最终的运行过程时,你会发现受限于目前物理存储与总线数据传输技术,大量的基于虚拟的技术应运而生,永远记住,IO是最花费时间的,能不IO就不IO,例如上面所讲的,为了避免因将磁盘上的数据加载到内存中的IO时间消耗,直接先不加载,用的时候再加载,这样不就不需要一次性大量的IO浪费时间且占用宝贵的内存了吗?

同样地,在上层应用中,也存在虚拟技术,最直接的就是虚拟机,通过软件模拟一台机器,这样,在这台机器上还能再跑一个操作系统,这样一层叠一层。

于是,我们开始理解,计算机是一个典型的层次结构,上一层是下一层的抽象,下一层为上一层提供基础性功能,上一层将这些功能在抽象简化后提供给上上一层使用,在整个层次系统的最上层就是我们用户,而最底层就是机器,是数字电路,是模拟电路,是物理规律。这或许是整个计算机系统的大体思想——分层次。

在计算机网络中你也会看到这样的典型的分层思想。

总之,目前为止,我们终于得到了一个可执行文件了(ELF格式),好了,我们要开始执行它了。

3.天杀的,我终于要执行了

好了,操作系统拿到这个可执行文件了,它要开始执行了,这一部分的内容就涉及到计算机组成原理了,在扯下去就偏题了,因此我不打算在这里再过多的说(主要是我也忘记差不多了,,,)

总之操作系统将可执行目标文件放在了内存上,CPU开始从内存中取指令到PC寄存器开始译码,取操作数,计算,得到结果写回操作数等一系列流水操作。并最终得到结果给用户。

至于操作系统又如何实现多进程,CPU又如何实现流水线技术,在缺页的时候如何产生中断到磁盘读数据等等问题就过于繁琐,不适合细谈了,得有待进一步系统性的学习,这里只是起一个引路的作用。

4.好了,终于结束了

你没看错,结束了。

不知道你是意犹未尽,觉得讲的细节太少,还是觉得云里雾里,不知所云。如果你刚刚上大学,刚刚步入大二或者大三,这些东西或许你听过一些,但不是全都懂,但这并没有什么关系,本人能力有限,绝非学霸,只能粗略地就大体框架系统讲一下自己的观点,细枝末节还有待你深入学习了。

如果你是大佬,你或许觉得这些简直就是小儿科,不足一提,你想要的细节性的技术性的完全没有。没错,确实如此,因为我也有很多东西不会,还需要深入了解,如果看完文章觉得我对于整个计算机学科的理解有偏差或者存在不足的话,真的请你能够帮我指出来,必感激不尽。

好了,这一篇的内容就此结束了。

2.深入一点理解C源程序的编译过程相关推荐

  1. openfoam安装中出现allmake error_深入理解 OpenFOAM 环境与编译过程

    深入理解 OpenFOAM 环境变量与编译 操作系统选择 由于 OpenFOAM 在 Linux 平台开发和测试,在非 Linux 平台无法直接对软件进行编译和安装,所以在非 Linux 平台上最简便 ...

  2. C/C++程序编译过程详解

    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接是把目标文件.操作 ...

  3. C++ 程序编译过程

    前言 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接是把目标文件 ...

  4. 【gcc/g++】1.编译器, 编译过程和基本参数

    "木叶飞舞之处" 一, gcc编译器 二, gcc的编译过程 1--完整版 1. 预处理 2. 编译 3. 汇编 4. 链接: 2--简化版 简化编译过程 3--编译完成 三, g ...

  5. 理解C语言(零) 导读(上):C程序的编译过程- 机器级表示

    1 从Hello world说起 Hello world是初学者使用任何一项编程语言最基本最简单的程序.下面是一个C语言版的"Helloworld" : #include < ...

  6. 【java】深入理解Java的动态编译

    文章目录 1.概述 2. 前提 3. 基本原理# 4. JDK动态编译 4.1 实现JavaFileObject 4.2 实现ClassLoader 4.3 实现JavaFileManager 4.4 ...

  7. 深入理解Java虚拟机-程序编译与代码优化

    本博客主要参考周志明老师的<深入理解Java虚拟机>第二版 读书是一种跟大神的交流.阅读<深入理解Java虚拟机>受益匪浅,对Java虚拟机有初步的认识.这里写博客主要出于以下 ...

  8. 深入理解计算机原理与编译原理,【底层原理:深入理解计算机系统】#1 一切从'hello world'说起 (一)...

    计算机系统是由硬件和系统软件组成的,他们共同工作来运行应用程序.虽然系统的具体实现方式随着时间不断的在变化,但是系统的内在概念却没有改变的. 所有的计算机硬件和软件有着相似的结构和功能.这个系列专题便 ...

  9. oracle中sga可以共享么,关于共享段与SGA的一点理解

    关于共享段与SGA的一点理解 上一周买了两本书,google了不少文档资料,看了看部分linux源代码终于有了以下一些理解.不知对不对 一 各类地址概念 1 逻辑地址:我们一般在用汇编语言写程序时可能 ...

最新文章

  1. 新款iPad真香,谁用谁知道!
  2. make 学习体会(一)
  3. 【客户下单】后台系统匹配分区关键字实现自动分单
  4. 【OpenCV 例程200篇】27. 图像的旋转(以任意点为中心)
  5. Android GPS及地磁传感器 API
  6. ERROR 1044 (42000): Access denied for user ''@'localhost' to database 'mysql'
  7. 哈夫曼编码+python实现
  8. Educational Codeforces Round 52: E. Side Transmutations(burnside引理)
  9. 2017.9.6.语文
  10. [Yii Framework] (转)CComponent基础类
  11. JVM内存管理------GC算法精解
  12. 华为路由器dhcp配置
  13. 智能鱼塘远程检测控制系统
  14. 五点差分法 matlab,【五点|五点差分法(matlab)解椭圆型偏微分方程】
  15. 3D世界 ORGE SceneManager GetStart
  16. 粉丝福利,抽5本《新程序员》004期免费送
  17. 安卓 获取机身内存,可用内存;运行内存,剩余内
  18. Altium Designer在由原理图导入PCB图报错:Number of nets in differential pair NET-TX+ is 1 instead of 2
  19. ProxySettings代理设置
  20. 图文详解丨iOS App上架全流程及审核避坑指南

热门文章

  1. 改变全局变量值得两种方法
  2. 共享打印机计算机名没有反应,共享打印机接收到打印任务,但是没有反应该怎么处理 - 系统之家...
  3. amd linux raid,RAID的详解
  4. Mac Office 怎么设置单面打印
  5. 读《曾国藩》笔记2--慈不掌兵
  6. AMI CORE8 OEM BIOS
  7. js 使用百度翻译api demo
  8. 如何使用KMS(2)
  9. FairyGUI增益BUFF数值改变的显示
  10. uefi +gpt 系统安装 和 传统legacy + mbr 的区别