1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 编程语言的自举之路——从机器码到高级语言

编程语言的自举之路——从机器码到高级语言

时间:2021-11-19 12:05:43

相关推荐

编程语言的自举之路——从机器码到高级语言

《编程语言的自举之路——从机器码到高级语言》源站链接,阅读体验更佳

语言是协议

在中国,大多数人使用汉语进行交流,我对你说一句汉语你能听懂并做出回应;如果有人对你说一句英语,而你又正好学习了英语,那么你也能听懂并做出回应。

可见我们平常所使用的自然语言是人与人之间交流的一种协议,不同的语言就是不同的协议,但是因为不同的语言都有一个统一的物理基础——地球,所有文明的诞生环境都是地球,所有语言的目的也都是一样的,那就是描述地球,记录一些物或者事,所以不同的自然语言之间可以相互翻译。而如果我们掌握了多门自然语言也就相当于掌握了多种描述这个世界的手段。

高级程序设计语言和自然语言有着相似之处:

高级程序设计语言都是用来指挥计算机干活的,是人与计算机的一种协议

所有的高级程序设计语言的诞生都是基于我们上一篇文章中介绍的计算机体系结构这样的物理基础,而所有的高级程序设计语言的最终目的就是产生计算机可以执行的指令和可以进行运算的数据,用来指挥计算机帮我们完成一系列的算法。

高级程序设计语言是程序员与程序员之间的协议

这就不得不提高级程序设计语言出现的目的了,因为直接使用机器语言或者是汇编语言进行程序开发不符合人类的思维习惯,而且信噪比太低,所以才诞生了高级程序设计语言。

高级程序设计语言更接近我们日常生活中所使用的自然语言,使用同一门高级程序设计语言的程序员都遵循一样的规则来编写代码,这样代码之中的逻辑就比较容易在程序员之间传播。

我一直信奉这样的一句话:傻瓜都会写出能够让机器理解的代码,只有好的程序员才能写出人类可以理解的代码。当然这句话有点偏执了,但这句话却说明——高级程序设计语言的源代码很大程度上是给人读的。

高级程序设计语言是程序员之间的协议,但是其首先是人与计算机之间的协议,那高级程序设计语言是怎么指挥计算机干活的呢?

高级程序设计语言的诞生

众所周知,计算机只认识0101这样的机器码,当然人类也可以读懂机器码,因为整个计算机世界都是人类创造的,但是人类甚至是制定机器码的人想要看懂机器码都得花费老大的力气。

这可不行,人类发明计算机是为人类服务的,是为了偷懒的,不能反过来把自己给累着,得想办法让机器码更容易阅读。

汇编语言

计算机所认识的机器码指令并不是无规律的0101,一条计算机指令一般分为代表动作的部分和代表操作目标的部分。

代表动作的部分是有限个的,每个都是具有特定含义的固定0101序列;对于这一部分,干脆把每一个0101序列都用一个单词代替,比如00000001代表的是将一个数据加载到某个寄存器里面,用loadi代替;00000011代表把某个内存地址中的数据加载到某个寄存器里面,用load代替;比如00000111代表把两个寄存器中的数据相加,则使用ADD代替。代表操作目标的部分根据代表动作的部分的不同具有不同的含义,有可能代表一个数据,有可能代表一个内存地址,也有可能代表一个用户可见的寄存器。对于数据,其实就是把人类理解的字符转化成了0101序列方便计算机理解,这一部分可以直接原样写或者用特殊的字符进行标识之后再原样写;对于内存地址,就是把一个十进制的数字转化成了二进制,不过十进制和二进制之间的转换不太直观,所以大都用地址值的十六进制代替;而对于用户可见的寄存器,只不过是寄存器的一个名字而已,我们同样可以用一个单词来代替(比如8086平台下的累加寄存器A)。

经过上面的处理,我们就成功用易于人类阅读的自然语言助记符对0101序列进行了替代,而且它也很容易转换成0101序列供计算机使用。

以后,我们再编写代码就不用直接使用0101了,只需要使用我们上面所定义的助记符来编写代码,然后再对照着助记符所对应的实际机器码把助记符代码转换成二进制机器码就可以运行了。

上面所说的助记符其实就是最简单的汇编助记符语言,而那些代表计算机动作的助记符的集合就构成了一个CPU平台的指令集。在某个CPU上运行的程序的所有功能都是依靠这个CPU平台所提供的指令集实现的,包括高级程序设计语言。

汇编语言的出现,为应用程序开发者提供了第一道屏蔽底层硬件的屏障,因为不同CPU平台的指令集和寄存器名所对应的0101序列可能是不一样的,但是不同的0101序列如果具有的功能是一样的,那我们就可以把不同的机器码映射到一套相同的助记符上面,这在很大程度上屏蔽了不同硬件平台的不同。

后来,汇编中又加入了一些宏指令、编译制导指令和伪指令,也具有了一定的可用性。但是汇编代码还是太靠近机器了。

比如,我们要写一个运算1+1并且把运算结果输出到屏幕上的程序,我们要事无巨细的把所有细节都告诉计算机:我们要告诉计算机把一个8位的数字1加载到AH,把一个8位的数字1加载到AL,把AL和AH中的数据相加并把结果保存到AH中,最后我们还要把AH中的计算结果Store到显存中以在屏幕上输出(上面的例子基于8086平台的指令集和寄存器)。

这要求软件开发人员对硬件资源有相当程度的理解,而且代码的信噪比简直低到令人发指——我只想写个1+1,还要自己组织内存和寄存器,与逻辑无关的信息太多了。

使用汇编操作内存也是非常不舒服的,比如在内存地址为0001的长度为2字节的存储区域上存了一个数字,而我在取的时候不小心写错了,那整个程序的逻辑就会出现错误。当然汇编中也不是只能使用内存地址来进行内存访问,也可以使用标识符;但是汇编中在声明一个标识符的时候只能声明这个标识符的步长,也就是想要申请的内存单元的个数,标识符本身代表的是所申请的存储区域的第一个内存单元的地址,我们在后续的使用中可能还是需要通过偏移量来进行访存;而且在进行内存访问的时候寻址方式也是多种多样的,这就让程序的可读性变得很低。

还存在一个很大的问题,对于存储在某个标识符所代表的内存空间中的数据,它是数字呢还是一个字符串呢还是一个地址信息呢?这些都是需要开发人员存在自己的大脑中的。而关键的问题是不管这些数据实际上具有什么样的作用,它在内存中的表现形式都是一堆0101,没有任何的不同,也就是说我们可以任意的理解这些数据,任意的对它们进行操作,这约束太少了,太过危险了。同时,因为其限制很少,程序员出错的概率也是居高不下。

高级程序设计语言

能不能让代码更贴近人类一些呢?高级程序设计语言应运而生。

高级程序设计语言更贴近人类,更易于人类阅读,非常接近于人们习惯使用的自然语言和数学语言,离机器更远,离人类更近,故称其为“高级语言”。比如我们要用C语言写上面所说的1+1的示例程序,代码为:printf("%d", 1+1);

怎么样,是不是有点见码知意的感觉了呢?

更重要的是,高级语言几乎把我们从硬件中解放了出来,高级程序设计语言为我们屏蔽了直接操作硬件资源的代码,在汇编中代表动作的指令全部都隐藏在了高级程序设计语言的上下文中,比如下面这段C语言代码:

int a = 1, b = 2;int c = a + b;printf("1 + 2 is %d", c);

上面短短的几行代码在转换为汇编的时候会有申请对应大小内存的操作、将数据加载到寄存器中的操作、对寄存器中的数据进行加法运算的操作,最后还有把寄存器中的数据写到显存的操作。

但是,事情还没有结束,计算机能运行的只有0101这样的机器码,我们在高级语言中连汇编的影子都很难看到,更别提0101这样的机器码了,但是高级语言想要在计算机上运行,我们势必要把高级语言的代码转换为0101这样的机器码。

汇编助记符和机器码之间是一一对应的关系,我们可以认为汇编代码只是机器码的助记符,所以汇编语言可以很容易转换成机器码,而把汇编代码转换成机器码的程序被称为汇编器。

把高级语言的代码转换为机器码就显得比较复杂了,因为它不像汇编语言那样和机器码之间存在着一一对应的关系,而且高级语言的运行方式也并不是唯一的,以C语言这种编译运行的语言为例,从编码结束到顺利运行需要经历编译->链接->运行这样的生命周期,而编译又分为词法分析、语法分析、语义分析等步骤,链接又分为静态链接和动态链接等手段。我们后面会有专门的文章来介绍高级语言是如何运行起来的,这里就不做过多赘述了。但是无论这个过程多么复杂,其本质上都是一种映射,这也就意味着高级语言和机器码之前其实存在着潜在的一对一的关系。

汇编语言和机器码的一一对应关系是明面上的,我们甚至可以认为有一张两列的表格,第一列是一条汇编指令,而第二列就是其对应的机器码。但是高级语言和机器码之间的一一对应关系是如何保证的呢?答案是由规则来保证的,一门高级语言一定会有自己的关键字,有规定的语法等等规则,我们在使用高级语言编写代码的时候,必须使用高级语言规定的关键字和语法,写出来的代码才能被这门高级语言的编译器处理成可以执行的机器码。

然而,万变不离其宗,高级语言在怎么高级,它最终都是要被转换为机器码的,这也就意味着,不同的高级语言肯定具有很多相通的地方,甚至我们可以总结出一门高级语言需要具备什么样的基本要素才能被顺利地转换为机器码,这就是我们下一篇文章将要介绍的内容。

其实,想要把高级语言代码直接转换为机器码是比较费力不讨好的,因为不同的硬件平台的差异可能是比较大的,而翻译高级语言的工作又是比较复杂的,如果我们直接把高级语言翻译成机器码,那么我们可能就需要在不同的硬件平台上都费时费力的做这项工作,这可不就是费力不讨好么。

前面我们曾经提到过,汇编语言为软件开发人员提供了第一道屏蔽硬件平台差异的屏障,因为不同的硬件平台的机器码可能是不一样的,但是我们可以把不同的机器码映射到一套相同的汇编助记符上,这样,在后续硬件平台升级的时候,虽然机器码在一直变化,但是它可以运行的汇编指令的变化就会小很多,也就是说硬件平台的指令集相对变得稳定下来,如此一来,我们何不先把高级语言的代码转换成汇编代码,然后再由汇编器进行下一步的动作呢。实际上,大部分编译运行的语言的编译器都是这么做的。

关于更多的细节,我会有专门介绍编译原理的文章,到时候欢迎惠读。

为什么高级程序设计语言如此之多

上文只是流水账式的说明了一下高级程序设计语言的诞生过程,随着计算机软件的不断发展,各种各样的高级程序设计语言层出不穷,到目前为止,编程语言应该有几百种了。

世界上有多种自然语言还是可以理解的,因为不同的文明虽然都是诞生于地球,但是诞生它们的文明是不同的,比如中文诞生于农耕文明,英文诞生于航海文明,人们面临的生存环境不同,不同的自然语言之间有所差异也是理所当然的。

类似的,虽然所有的高级程序设计语言的诞生都是基于相同的计算机体系结构,但是它们想要解决的问题领域却是不同的。

这就好比大厨的工具箱中的刀具有很多种一样,不同的刀具适合处理不同的食材,编程语言也是一样的,不同的语言有不同的特性,有不同的生态,适合应用于不同的问题领域,在某个领域使用某种语言能达到产投比的最大化。比如在嵌入式领域,汇编和C是首选;在操作系统领域,C是首选;在系统级服务编程领域,C++是首选;在企业级应用程序和Web应用领域,Java是首选。

那么我们有必要学习和掌握多门编程语言吗?说的现实一点,我们所做的任何努力都是有所求的,也就是说我们在学习编程语言的时候要根据自己的工作领域,以提高自己的生产力和行业价值为主要目的去学习,这就是传说中的面向人民币编程。当然,如果是兴趣使然,则可以多涉猎一些,但是在学习的过程中一定要有侧重,只有选好自己的方向和领域,明确哪些技术能给自己带来收益,而又有哪些技术是用以扩展视野和提升思维的,才能不“乱码渐欲迷人眼”。

本人为自己制定的计划就是,主攻JavaWeb领域,同时学习其他的编程语言来扩展自己的视野,提升自己的思维境界。在我后续的文章中会有关于多门编程语言基本特性介绍的文章。

鸡生蛋还是蛋生鸡?软件世界一直在自举

把汇编语言转换成机器码的动作是汇编器帮我们完成的,把高级语言转换成机器码的动作是编译器帮我们完成的。而汇编器和编译器都是一种软件,但是它们最初是怎么被开发出来的呢?

毫无疑问,世界上第一个汇编器肯定不是用汇编语言编写的,同样的,世界上第一个C语言的编译器也肯定不是用C语言开发的,这是一个典型的鸡或蛋的问题。

世界上第一个汇编器肯定需要使用机器码进行编写,等到这个用机器码开发的汇编器可以把现有的所有的汇编语言的指令集完整无误得转换为机器码的时候,它的使命就可以结束了,因为对后续汇编器的升级,我们完全可以基于现有的汇编语言编写出新版本的汇编器,然后用现有的汇编器汇编新版的汇编器,这样,在以后汇编语言的升级迭代中就不需要机器语言的参与了。C语言的过程也是类似的,而现在新版本的C语言的编译器也都是用C语言来进行编写的。

**当一门语言可以用自己实现自己的编译器的时候,我们就说一门语言实现了自举,**更多关于自举的内容大家可以参考维基百科。

不止是在编程语言的发展上,其实,“自举”这种现象在软件世界中是非常常见的,比如现在我们的应用软件的运行环境一般也都不是裸机了,而是运行在操作系统之上的,从大的方面来说,软件世界其实一直是在自举的向前发展的。

感谢你耐心读完。本人深知技术水平和表达能力有限,如果文中有什么地方不合理或者你有其他不同的思考和看法,欢迎随时和我进行讨论(laomst@)。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。