Knighthana
文章95
标签138
分类7

文章归档

一些程序设计基础与编译原理课程方面的常识

一些程序设计基础与编译原理课程方面的常识

一些程序设计基础与编译原理课程方面的常识

介绍一些来自编译原理的常识

可能这些常识对于只有程序设计基础知识、但是对此好奇的人来说很头痛,因为分不清,搞不明白,查了资料也是一知半解;

但是它们在编译原理中仅仅只是前导内容,因此现在把它整理出来,用明了的方式解释清楚,让更少的人为此头痛;

当然,最准确的解释还是得看书本,我这样用一种直观地方式解释,其实只注重在“明了”方面的程度,肯定会有失准确性;如果有人觉得有更好的、兼备明了与准确性的措辞欢迎与我交流;

编译器 VS 解释器

提到编译器不要立刻想到源代码变成汇编语言,就想到机器语言代码,就想到一串1111111100001001000,然后就迷失得找不到自我了,然后再看解释器,由于人已经处在混沌之中,就会觉得这东西长得和编译器很像什么的;

先别慌,先把这两个词分清,一个叫“编译器”,另一个叫“解释器”;

“编译器”其实并不局限于将高级语言的源代码编译成为机器语言代码(或者先编译成汇编语言代码,再交由汇编程序转换成机器码)这一种功能,因为编译器其实可以理解成“翻译器”,它具有更加强大的功能

所谓编译器,要做的就是把源语言的程序代码“翻译成为”目标语言的程序代码,就这么简单;源语言可以是高级语言,但也可以是汇编语言,目标语言可以是机器语言,但是也可以是别的什么,甚至可以是高级语言,编译器只是负责把一种语言翻译成另一种语言,仅此而已;

而解释器是一个程序,它被用户调出来之后就一直在运行着,当接收到用户的输入之后,它就会按照用户的输入去做各种工作,只不过这里的输入恰好是某种可以被解释解释的程序设计语言以及此时用户的现场输入;解释器也不会产生所谓“目标代码”的东西,因为它从一开始就根本不是为了这个用途这么设计的;解释器很像一双手套,“运行”由高级语言设计的逻辑意义上的程序时,还是没有脱离这个解释器,解释器还是在主存里面待着,CPU认的也是解释器而不是什么逻辑意义上的高级语言程序;

“有没有目标代码”这一个区别是从用途来很清楚明了地解释这个问题,

形象化一点地理解,假如有一种程序设计语言既可以被编译执行,也可以被解释执行,那么使用编译器把某种语言的源代码翻译成了机器语言的代码文件,和用解释器去解释源代码文件执行各种动作——这也是最常见的用法,这二者会有的一个显著区别就是:所需要执行的这种动作由谁来完成;

如果是编译得到的机器语言代码文件,那么就是编译得到的由机器语言编写的二进制文件自己在执行这些动作,也就是说,什么时候将这个机器语言代码文件装入内存,把它的首地址装入程序计数器,让CPU把它执行起来,它才会在什么时候开始发挥作用,要等它开始跑了,再往里面灌输入等着它给输出;

对应地,解释器那边,需要先把解释器装入主存,把解释器在主存的首地址装入程序计数器,让它开动起来,有了一个解释器的运行时,然后往解释器里面灌代码以及输入,解释器会作为一个“手套”去执行这些动作并给出输出;

这样一看,这两个东西从根源上就如此地不同,仅仅只是“同样都可以用来让高级语言的源代码发挥作用”,就被狠狠地混淆了;

AOT VS JIT

AOT:Ahead Of Time

JIT:Just In Time

AOT和JIT都是编译器范畴的,没错,可能会在一些解释型程序设计语言那边看到“JIT”这种概念,但是这俩都是属于编译执行的;理解这个概念需要先清清楚楚明明白白地把什么是“编译”什么是“解释”搞懂;

AOT这种方式只有对代码进行编译才能做到;

JIT则很具有迷惑性:

一般来说会弄混的情况是Java,这个特立独行的语言支持先将Java高级语言源代码编译成为“字节码”,这一步是纯粹的编译;再交给“Java Runtime”(各个品牌的JRE都算Java Runtime的,现在2023年比较流行的两种是Oracle的JRE和OpenJDK的JRE)这个虚拟机负责解释执行,这一步又变成了解释;

在Java高级语言到字节码这一步,全都毫无疑问地是编译,全都是“AOT”,虽然这个“Time”并不是真正执行它的“Time”,但假如我们说拿到字节码这一步就算跑完了,那就是;

然而,在运行起来服务人类期间如何解释字节码里面的东西的时候,不同的运行时搞出了不同的玩法;

传统解释型的就是作为解释器,把字节码作为一种需要解释的程序设计语言,和输入一起灌进虚拟机扮演的解释器,解释器解释明白了,就会执行各种动作,给出输出;

然而JRE还有一个流派就是JIT,这个流派的运行时会在处理输入的前一刻将字节码编译成为机器语言,然后去执行这个机器语言程序,让这个新鲜出炉的机器语言程序执行各种动作,给出输出,而不是自己去执行动作或者给输出;

那么其它的经常用于解释型的程序设计语言为什么也会有JIT呢?

用JRE的这个过程去看,JRE是接收字节码和输入,然后用不同的方式,也即“解释执行”和“JIT”两种方式给出执行与输出;

这些解释型程序设计语言的RE也是类似的,只不过它们接收由高级语言编写的人可以看懂的程序代码输入,随后,不同的流派用不同的方式给出了执行与输出,其中解释派是单纯地用解释器进行解释,而JIT派则是在拿到输入,确定类型之后即刻开始编译得到机器语言代码,然后把机器语言代码装入主存,由机器码去做交给它的工作;

静态 VS 动态

静态:Static

动态:Dynamic

静态类型、动态类型、强类型、弱类型,十分混沌邪恶的四个维度;

今天不扯这些,我们只说“静态”和“动态”是什么场景,剩下的自己慢慢想;

这里掉一下书袋,来自CPTT机械工业出版社中译本第2版P15对于“静态”和“动态”的解释,我觉得我来解释一遍也不如原书的明了:

如果一个语言使用的策略支持编译器静态决定某个问题,那么我们就说这个语言使用了一个静态策略(static policy),或者说这个问题可以在编译时刻(compile time)决定;

一个只允许在运行程序的时候作出决定的策略被称为动态策略(dynamic policy),或者被认为需要在运行时刻(run time)作出决定。

与原书不同,我把动态策略也完全翻译成了static policy,以与下面的dynamic policy对应相照

所以说,静态策略的语言能够在编译时敲定一些东西,比如变量的类型之类的,这样可以将程序的机器代码在执行之前就完全编译出来,运行的时候效率就会比较高;

反之,动态策略的语言无法在编译时敲定这些东西,比如还是变量的类型这个例子,它必须等到运行的时候,用户给进来一个输入,这时候才能确定,哦原来是这么个类型,很显然,这样的策略给代码编写带来了很大的灵活性,比如某些脚本语言的变量类型就可以一会一变;但是有利就有弊,使用这种策略的语言在处理输入的语句中必须拿到用户的输入才能确定类型,才能进行下一步,于是很明显地,这种语言设计出来的玩意想要在用户调用之前提前编译好是有难度的;

原书下面还提了一下“静态”与“动态”的“作用域”,作用域、命名空间什么的,这几个东西算是同一个范畴,且看原书的解释:

x的一个声明的作用域是指程序的一个区域,在其中对x的使用都指向这个声明;

如果仅通过阅读程序就可以确定一个声明的作用域,那么这个语言使用的是静态作用域(static scope),或者说词法作用域(lexical scope)。

否则,这个语言使用的是动态作用域(dynamic scope)。如果使用动态作用域,当程序运行时,同一个对x的使用会指向x的几个声明中的一个。

大部分语言(比如C和Java)使用静态作用域。

注意,C里面对“同一个”变量名给出不同层次的声明可不是动态作用域,因为C的代码通过阅读便能了解此刻用的是“这个名字”的哪个声明(其实本质上完全是不同的变量),比如局部变量的优先级就比全局变量的优先级要高,这些都不需要等到运行起来了再做判断,编译时就能敲定,给出一个强硬的相关关系;

动态作用域会在声明时带上变量使用时间的信息,这样真的除了在运行的时候确定以外别无他法;

声明 VS 定义

这种问题一般会用来考验可怜的大一新生freshman,当然,对这个问题的了解程度不同,给出的答案也不同,这种东西向来是答错了也不要紧,能体现出了解程度就行,也就是有区分度即可;

比如我当年被问到这个问题的时候扯了半天的“内存”什么的,其实当时的回答也不能算完全对,但是有区分度也就行了;

声明用来确定一个事物的类型;

定义用来确定一个事物的值;

就这么简单;

注意,这里说的是“事物”,并没有说是“变量”,也就是说,变量和过程(C中称作函数的那个,OOPL——面向对象程序设计语言里面唤作方法的那个)都是有自己的“声明”和“定义”的;

奇妙吧?多学习,以后还有更奇妙的

结点 VS 节点

一个普通程序员搞不清结点和节点可能比较正常,但是作为科班出身的计算机领域工作者,必须搞清楚结点和节点,不要在自己写的东西里面闹笑话;

结点是一个存储单元,链表的结点是一个“结点”,树的“结点”是一个结点,这些结点里面可能存了变量名、地址指针之类的东西,但是,结点自身是一个静态的东西,如果外力不去更改它,它不会乱动;

节点与结点完完全全不同,节点带有计算能力,甚至有自己的维护能力,我们可以说一个大型分布式处理器中的一个运算单元是一个节点,也可以说一个由一堆嗡嗡作响的服务器和一群运维人员组成的大型数据中心是一个节点,但唯独不会说一段内存中的静止数据是“节点”

Knighthana

2023/04/09