程序编译过程分析
在日常的学习和工作中, 我们常常使用IDE(集成开发环境,Integrated Development Environment)进行开发,比如嵌入式经常用到的Keil, IAR等等。当我们编写一段代码后,只需要轻轻一点IDE中编译按钮,工具就会自动帮我们生成可执行文件 。IDE的“智能化”在背后帮我们做了很多工作,从一个.c源文件是如何一步一步生成可执行文件的呢? 今天我们就分析下背后的原理和过程。
我们还是以最经典的HelloWorld为例子,这里用C语言来编写:
编译过程可以分四个阶段:
- 预处理(预编译):(.c—>.i)
- 将以\”include\”格式包含的文件复制(或叫原地展开)当前源文件中
- 用实际值替换用”define“宏定义的符号
- 根据“#if”后的条件决定需
使用-E参数来生成.i文件,具体指令:
我们用vi打开HelloWorld.i文件查看下内容:
从内容我们可以看出,短短的几行HelloWorld.c在预处理后却是高达800多行代码,其中前面的800多行都stdio.h的内容,全都包含进来了,只有最后的几行代码才是我们写的~ 相信这么大家都理解预处理的作用了吧。
2. 编译: (.i—>.s)转换为汇编语言文件
这个阶段编译器主要做词法分析,语法分析,语义分析等,检查无错误后,把代码翻译成汇编语言。
使用-S参数来生成.s文件,具体指令:
我们用vi打开HelloWorld.s文件查看下内容:
可以看到.s文件的内容都是一些汇编指令,与我们的预想是一致的。
3. 汇编: (.s—>.o) 得到目标文件(在Windows中.obj文件格式,linux中.o文件)
我们知道在计算器中只认识机器语言,即0或1,因此在上一步编译得到的汇编文件(汇编本质就是机器指令的助记符,什么是助记符呢? 比如机器码1000010000101111,用asd来代替,相信大家更倾向于记asd,而不是一大串二进制码吧~~) , 而汇编就是将汇编代码转为机器代码,本质就是一种翻译的过程。
使用-c参数来生成.o文件,具体指令:
可以看到成功生成了HelloWorld.o文件, 上面说过汇编就是翻译汇编指令为机器指令, 因此.o内容都是二进制(0和1),故我们将不打开看拉~
4. 链接:到了最后一步,并是将之前得到的各个机器代码和库文件(当然也是机器代码)按照一定规则合并成一个可执行的文件。
在gcc编译工具链中使用ld命令进行链接,考虑到链接时需要提供链接规则或指定脚本配置,这里为了简化学习,我们可以直接使用gcc工具链提供的默认规则,直接调用gcc来链接即刻。
其中-o是指定生成可执行文件名(随意,如果不指定默认生成名a.out), 我们来执行下:
成功打印了“Hello world.”信息
好了,经过上面所述,相信大家都能清楚从一个源文件编译生成可执行文件的过程拉: 源文件(.c, .cpp) —> 预处理(.i) —> 编译(.s) —> 汇编(.o) —> 链接(elf或.exe可执行文件)。 好啦,今天的分析到此,对嵌入式,物联网感兴趣的小伙伴,记得点击收藏,转发,关注,不迷路~
汇编指令入门级整理 | 原力计划
作者 | AlbertS
出品 | CSDN 博客
前言
我们大都是被高级语言惯坏了的一代,源源不断的新特性正在逐步添加到各类高级语言之中,汇编作为最接近机器指令的低级语言,已经很少被直接拿来写程序了,不过我还真的遇到了一个,那是之前的一个同事,因为在写代码时遇到了成员函数权限及可见性的问题,导致他无法正确调用想执行的函数,结果他就开始在 C++ 代码里嵌入汇编了,绕过了种种限制终于如愿以偿,但是读代码的我们傻眼了…
因为项目是跨平台的,代码推送的 Linux 上编译的时候他才发现,汇编代码的语法在 Linux 和 Windows 上居然是不一样的,结果他又用一个判断平台的宏定义“完美”的解决了,最终这些代码肯定是重写了啊,因为可读性太差了,最近在学习左值、右值、左引用和右引用的时候,总是有人用程序编译生成的中间汇编代码来解释问题,看得我迷迷糊糊,所以决定熟悉一下简单的汇编指令,边学习边记录,方便今后忘记了可以直接拿来复习。
什么是汇编语言
汇编语言是最接近机器语言的编程语言,引用百科中的一段话解释为:
汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。汇编语言又被称为第二代计算机语言。
汇编语言产生的原因
对于绝大多数人来说,二进制程序是不可读的,当然有能人可以读,比如第一代程序员,但这类人快灭绝了,直接看二进制不容易看出来究竟做了什么事情,比如最简单的加法指令二进制表示为 00000011,如果它混在一大串01字符串中就很难把它找出来,所以汇编语言主要就是为了解决二进制编码的可读性问题。
汇编与二进制的关系
换句话来说,汇编语言就是把给机器看的二进制编码翻译成人话,汇编指令是机器指令的助记符,与机器指令是一一对应的关系,是一种便于阅读和记忆的书写格式。有效地解决了机器指令编写程序难度大的问题,并且使用编译器,可以很方便的把汇编程序转译成机器指令程序,比如之前提到的 00000011 加法指令,对应的汇编指令是 ADD,在调用汇编器时就会把 ADD 翻译成 00000011。
寄存器
说到汇编指令不得不提到寄存器,寄存器本身是用来存数据的,因为 CPU 本身只负责逻辑运算,数据需要单独储存在其他的地方,但是对于不熟悉寄存器的人来说会有疑惑,数据不是存在硬盘上吗?或者说数据不是存在内存中吗?这些想法都没错,那么寄存器是用来做什么的呢?
寄存器作用
其实硬盘、内存都是用来存储数据的,但是 CPU 的运算速度远高于内存的读写速度,更不用说从硬盘上取数据了,所以为了避免被拖慢速度影响效率,CPU 都自带一级缓存和二级缓存,一些 CPU 甚至增加了三级缓存,从这些缓存中读写数据要比内存快很多,但是还是无法使用飞速运转的 CPU,所以才会有寄存器的存在。
寄存器不是后来增加的,在最初的计算中就已经设计出来,相比而言,多级缓存出现的更晚一些,通常那些最频繁读写的数据都会被放在寄存器里面,CPU 优先读写寄存器,再通过寄存器、缓存跟内存来交换数据,达到缓冲的目的,因为可以通过名称访问寄存器,这样访问速度是最快的,因此也被称为零级缓存。
存取速度比较
通过上面的叙述我们可以知道存取速度从高到低分别是: 寄存器 > 1级缓存 > 2级缓存 > 3级缓存 > 内存 > 硬盘,关于它们的存取速度,举个例子很容易就能明白了,比如我们做菜(CPU工作)时,取手中(寄存器)正拿着的肉和蔬菜肯定是最快的,如果没有就需要把案板上(1级缓存)处理好的菜拿过来,如果案板上没有就在更远一点的洗菜池(2级缓存)中找一找,还没找到的话就要到冰箱(3级缓存)中看一看了,这时发现家里真没有,那去楼下的菜店(内存)去买点吧,转了一圈发现没有想要的,最后还是开车去农贸市场(硬盘)买吧。
通过上面这个例子应该能明白它们的速度关系了,既然缓存这么快,为什么不用缓存代替内存,或者将2、3级缓存都换成1级缓存呢?这里边有一个成本问题,速度越快对应着价格越高,如果你买过机械硬盘和固态硬盘应该很容易就理解了。
寄存器分类
常用的 x86 CPU 寄存器有8个:EAX 、EBX、ECX、EDX、EDI、ESI、EBP、ESP,据说现在寄存器总数已经超过100个了,等我找到相关资料再来补充,上面这几个寄存器是最常用的,这些名字也常常出现在汇编的代码中。
我们常说的32位、64位 CPU 是指数据总线的宽度或根数,而寄存器是暂存数据和中间结果的单元,因此寄存器的位数也就是处理数据的长度与数据总线的根数是相同的,所以32位 CPU 对应的寄存器也应该是32位的。
常用寄存器用途
上面提到大8个寄存器都有其特定的用途,我们以32位 CPU 为例简单说明下这些寄存器的作用,整理如下表:
寄存器EAX、AX、AH、AL的关系
在上面的图标中每个常用寄存器后面还有其他的名字,它们是同一个寄存器不同用法下的不同名字,比如在32位 CPU 上,EAX是32位的寄存器,而AX是EAX的低16位,AH是AX的高8位,而AL是AX的低8位,它们的对照关系如下:
汇编语言指令
终于说到汇编常用指令了,因为 linux 和 windows 下的汇编语法是有些不同的,所以下面我们先通过 windows 下的汇编指令来简单学习一下,后续再来比较两者的不同。
数据传送指令
算术运算指令
逻辑运算指令
循环控制指令
转移指令
Linux 和 windows 下汇编的区别
前面说到 linux 和 windows 下的汇编语法是不同的,其实两种语法的不同和系统不同没有绝对的关系,一般在 linux 上会使用 gcc/g++ 编译器,而在 windows 上会使用微软的 cl 也就是 MSBUILD,所以产生不同的代码是因为编译器不同,gcc 下采用的是AT&T的汇编语法格式,MSBUILD 采用的是Intel汇编语法格式。
总结
-
汇编指令是机器指令的助记符,与机器指令是一一对应的
-
AT&T的汇编语法格式和Intel汇编语法格式的是不同的
-
常用寄存器:EAX 、EBX、ECX、EDX、EDI、ESI、EBP、ESP
-
存取速度从高到低分别是: 寄存器 > 1级缓存 > 2级缓存 > 3级缓存 > 内存 > 硬盘
-
常用的汇编指令:mov、je、jmp、call、add、sub、inc、dec、and、or
版权声明:本文为CSDN博主「AlbertS」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/albertsh/article/details/106041560
☞华为全球分析师大会:HMS Core全球开发者应用集成的数量加速增长,打造全场景智慧体验
汇编和编译是什么
汇编和编译是什么,有什么区别和联系
不少学过计算机的朋友都知道,很多语言都要经过编译、汇编转化为底层机器码再计算机执行。
而考研计算机408的计算机组成原理部分也会简单涉及到汇编语言,上面大概讲的是源代码经过编译得到汇编代码,汇编代码经过汇编得到机器代码。
今天简单讲一下什么是编译和汇编以及这两者的区别和关系。
汇编
汇编是将汇编语言转换为机器语言的过程。
汇编语言,Assembly Language
以及汇编程序,Assembler
汇编语言使用助记符代替操作码,用地址符号代替地址码,这样就把机器语言的二进制码转换成了咱们经过修炼后勉强看得懂的形式。
汇编语言的每一行通常对应机器语言中的一个或几个指令。
汇编过程由汇编器完成,它将汇编语言转换成对应的二进制代码,生成目标文件。
通常来讲不同的处理器架构要使用不同的汇编语言,常用的处理器架构比如x86、ARM。
汇编语言在一些接近底层但不是底层的位置有得天独厚的优势,比如嵌入式系统、操作系统内核、驱动程序等等。
汇编语言是非常多程序员、工程师曾经的噩梦。
编译
编译是将高级语言源代码转换成机器语言目标代码的过程。
高级语言通常指的是C、C++、Java等等我们这些码农和程序员经常直接使用的编程语言。
编译过程由编译器完成,它将一个或者多个源代码文件转换成一个或多个目标文件。
编译过程通常包含词法分析“”语法分析、语义检查、中间代码生成、代码优化、目标代码生成等多个步骤。
汇编与编译的区别和关系
汇编语言是相对更加底层、与硬件联系更多的低级语言。
而编译的语言是相对不怎么底层,面向开发者的高级语言。
汇编程序生成的目标代码的效率通常会比编译程序生成目标代码更高,毕竟汇编语言更加接近底层。
另一方面,汇编语言与特定机器和环境紧密相关,因此其移植性可能会差一些,编译语言的可移植性、跨平台性通常会高一些。
通常广义编译包括预处理、编译、汇编、连接,也就是说汇编其实是广义编译的一部分。
编译器在将高级语言转换成机器语言的过程中,通常会先将源代码的一部分转换成汇编语言形式,这个就是广义编译中的狭义编译部分。
然后再由内置的汇编器或外部汇编器进一步处理,这个就是广义编译中的汇编部。
编译生成的目标文件通常需要链接器Linker来处理,它会将多个目标文件和库文件中的符号引用连接起来,形成一个完整的可执行程序。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。