· GTC认證翡翠鉴定评估师、艺术优质答主
你描述的斑就是色斑正常的
网上买,目测小四位数范围
店里、商场、景点会贵很多
你描述的斑就是色斑正常的
网上买,目测小四位数范围
店里、商场、景点会贵很多
下载百度知道APP抢鮮体验
使用百度知道APP,立即抢鲜体验你的手机镜头里或许有别人想知道的答案。
我们提到了LISP中,因为eval的原因,发展出了运行时环境这样一个概念基于这个概念,ㄖ后发展出了虚拟机技术但这段历史并不是平铺直叙的,实际上这里面还经历了一个非常漫长而曲折的过 程, 说起来也是非常有意思嘚 这一节我们就着重解释虚拟机的历史。
我们21世纪的程序员凡要是懂一点编程技术的,基本上都知道虚拟机和字节码这样两个重要的概念 所谓的字节码(), 是一种非常类似于机器码的指令格式这种指令格式是以二进制字节为单位定义的(不会有一个指令只用到一个字節的前四位),所以叫做字节码所谓的虚拟机, 就是说不是一台真的计算机而是一个环境,其他程序能在这个环境中运行而不是在嫃的机器上运行。现在主流高级语言如Java, Python, PHP, C#编译后的代码都是以字节码的形式存在的, 这些字节码程序 最后都是在虚拟机上运行的。
1.虚拟機的安全性和跨平台性
虚拟机的好处大家都知道最容易想到的是安全性和跨平台性。安全性是因为现在可执行程序被放在虚拟机环境中運行虚拟机可以随时对程序的危险行为, 比如缓冲区溢出数组访问过界等等进行控制。跨平台性是因为只要不同平台上都装上了支持哃一个字节码标准的虚拟机程序就可以在不同的平台上不加修改而运 行,因为虚拟机架构在各种不同的平台之上用虚拟机把下层平台間的差异性给抹平了。我们最熟悉的例子就是Java了Java语言号称一次编写,到处运行(Write Once, Run Anywhere)就是因为各个平台上的Java虚拟机都统一支持Java字节码,所以鼡户感觉不到虚拟机下层平台的差异
虚拟机是个好东西,但是它的出现不是完全由安全性和跨平台性驱使的。
我们知道在计算机还昰锁在机房里面的昂贵的庞然大物的时候,系统软件都是硬件厂商附送的东西(是比尔盖茨这一代人的出现才有了和硬件产业分庭抗礼嘚), 一个系统程序员可能一辈子只和一个产品线的计算机打交道压根没有跨平台的需求。应用程序员更加不要说了因为计算机很稀囿,写程序都是为某一台计算机专 门写的所以一段时间可能只和一台庞然大物打交道,更加不要说什么跨平台了 真的有跨平台需求,昰从微型计算机开始真的普及开始的因为只有计算机普及了,各种平台都被广泛采用了相互又不互相兼容软件,才会有软件跨平台的需求 微机普及的历史,比PC普及的历史要早10年而这段历史,正好和UNIX发展史是并行重叠的
熟悉UNIX发展史的读者都知道,UNIX真正普及开来是洇为其全部都用C,一个当时绝对能够称为跨平台的语言重写了一次又因为美国大学和科研机构之间的开源共享文化,C版本的UNIX出生没多久就迅速从原始的PDP-11实现,移植到了DECIntel等平台上,产生了无数衍生版本随着跨平台的UNIX的普及, 微型计算机也更多的普及开来因为只需要掌握基本的UNIX知识,就可以顺利操作微型计算机了所以,微机和UNIX这两样东西都在1970年 到1980年在美国政府大学,科研机构公司,金融机构等各种信息化前沿部门间真正的普及开来了这些历史都是人所共知耳熟能详的。
既然UNIX是跨平台的那么,UNIX上的语言也应当是跨平台的 (注:夲节所有的故事都和Windows无关因为Windows本身就不是一个跨平台的操作系统)。UNIX上的主打语言C的跨平台性一般是以各平台厂商提供编译器的方式實现的,而最终编译生成的可执行程序其实不是跨平台的。所以跨平台是源代码级别的跨平台,而不是可执行程序层面的 而除了标准了C语言外,UNIX上有一派生机勃勃的跨平台语言就是脚本语言。(注:脚本语言和普通的编程语言相比在能完成的任务上并没有什么的巨大差异。脚本语言往往是针对特定类型的问题提出的语法更加简单,功能更加高层常常几百行C语言要做的事情,几行简单的脚本就能完成)
脚本语言美妙的地方在于它们的源代码本身就是可执行程序,所以在两个层面上都是跨平台的不难看出,脚本语言既要能被矗接执行又要跨平台的话,就必然要有一个“东西”横亘在语言源代码和平台之间,往上在源代码层面,分析源代码的语法结构囷逻辑,也就是所谓的“解释”;往下要隐藏平台差异,使得源代码中的逻辑能在具体的平台上以正确的方式执行,也就是所谓的“執行”
虽说我们知道一定要这么一个东西,能够对上“解释”对下“执行”,但是“解释”和“执行”两个模块毕竟是相互独立的洇此就很自然的会出现两个流派:把解释和执行设计到一起和把解释和执行单独分开来这样两个设计思路,需要读者注意的是现在这两個都是跨平台的,安全的设计而在后者中字节码作为了解释和执行之间的沟通桥梁,前者并没有字节码作为桥梁
4.解释和执行在一起的方案
我们先说前者,前者的优点是设计简单不需要搞什么字节码规范,所以UNIX上早期的脚本语言都是采用前者的设计方法。 我们以UNIX上大洺鼎鼎的AWK和Perl两个脚本语言的解释器为例说明AWK和Perl都是UNIX上极为常用的,图灵完全的语言其中AWK,在任何UNIX系统中都是作为标准配置的,甚至入选IEEE POSIX標准是入选IEEE POSIX卢浮宫的唯一同类语言品牌,其地位绝对不是UNIX下其他脚本语言能够比的这两个语言是怎么实现解释和运行的呢? 我从AWK的标准实现中摘一段代码您一看就清楚了:
熟悉Yacc的读者应该能够立即看出, AWK调用了Yacc解析源代码生成了一棵语法树。按照winner
的定义,winner
是这棵语法树的根節点
在“解释”没有任何错误之后,AWK就转入了“执行” (compile_time变成了0)将run
作用到这棵语法树的根节点上。
不难想像这个run
函数的逻辑是递归的(事实上也是),在语法树上从根依次往下,执行每个节点的子节点然后收集结果。是的这就是整个AWK的基本逻辑:对于一段源代码,先鼡解释器(这里awk用了Yacc解释器),生成一棵语法树然后,从树的根节点开始往下用run这个函数,遇山开山遇水搭桥,一路递归下去最後把整个语法树遍历完,程序就执行完毕了(这里附送一个小八卦,抽象语法树这个概念是LISP先提出的因为LISP是最早像AWK这样做的,LISP实在是屬于开天辟地的作品!)Perl的源代码也是类似的逻辑解释执行的我就不一一举例了。
现在我们看看这个方法的优缺点 优点是显而易见的,因为通过抽象语法树在两个模块之间通信避免了设计复杂的字节码规范,设计简单但是缺点也非常明显。最核心的缺点就是性能差需要资源多,具体来说就是如下三个缺点。
缺点1因为解释和运行放在了一起,每次运行都需要经过解释这个过程假如我们有一个腳本,写好了就不修改了只需要重复的运行,那么在一般应用下尚可以忍受每次零点几秒的重复冗余的解释过程在高性能的场合就不能适用了。
缺点2因为运行是采用递归的方式的,效率会比较低我们都知道,因为递归涉及到栈操作和状态保存和恢复等代价通常比較高,所以能不用递归就不用递归在高性能的场合使用递归去执行语法树,不值得
缺点3,因为一切程序的起点都是源代码而抽象语法树不能作为通用的结构在机器之间互传,所以不得不在所有的机器上都布置一个解释+运行的模块在资源充裕的系统上布置一个这样的系统没什么,可在资源受限的系统上就要慎重了比如嵌入式系统上。 鉴于有些语言本身语法结构复杂布置一个解释模块的代价是非常高昂的。本来一个递归执行模块就很吃资源了再加一个解释器,嵌入式系统就没法做了所以, 这种设计在嵌入式系统上是行不通的
當然,还有一些其他的小缺点比如有程序员不喜欢开放源代码,但这种设计中一切都从源代码开始,要发布可执行程序就等于发布源代码,所以不愿意 公布源代码的商业公司很不喜欢这些语言等等但是上面的三个缺点,是最致命的这三个缺点,决定了有些场合僦是不能用这种设计。
前面的三个主要缺点恰好全部被第二个设计所克服了。在第二种设计中 我们可以只解释一次语法结构,生成一個结构更加简单紧凑的字节码文件这样,以后每次要运行脚本的时候 只需要把字节码文件送给一个简单的解释字节码的模块就行了。洇为字节码比源程序要简单多了所以解释字节码的模块比原来解释源程序的模块要小很多;同时, 脱离了语法树我们完全可以用更加高性能的方式设计运行时,避免递归遍历语法树这种低效的执行方式;同时在嵌入式系统上,我们可以只部署运行时不部署 编译器。 這三个解决方案预示了在运行次数远大于编译次数的场合,或在性能要求高的场合或在嵌入式系统里,想要跨平台和安全性就非得鼡第二种设计,也就是字节 码+虚拟机的设计
讲到了这里,相信对Java,对PHP或者对Tcl历史稍微了解的读者都会一拍脑袋顿悟了:原来这些牛逼的虚拟機都不是天才拍脑袋想出来的而是被需求和现实给召唤出来的啊!
我们先以Java为例,说说在嵌入式场合的应用Java语言原本叫Oak语言,最初不昰为桌面和服务器应用开发的而是为机顶盒开发的。SUN最初开发Java的唯一目的就是为了参加机顶盒项目的竞标。嵌入式系统的资源受限程喥不必细说了自然不会允许上面放一个解释器和一个运行时。所以不管Java语言如何,Java虚拟机设计得直白无比简单无比,手机上智能鉲上都能放上一个Java运行时(当然是精简版本的)。 这就是字节码和虚拟机的威力了
SUN无心插柳,等到互联网兴起的时候, Java正好对绘图支持非瑺好在Flash一统江湖之前,凭借跨平台性能以Applet的名义一举走红。然后又因为这种设计先天性的能克服性能问题,在性能上大作文章凭借JIT技术,充分发挥上面说到的优点2再加上安全性,一举拿下了企业服务器市场的半壁江山这都是后话了。
再说PHPPHP的历史就包含了从第┅种设计转化到第二种设计以用来优化运行时性能的历史。PHP是一般用来生成服务器网页的脚本语言一个大站点上的PHP脚本,一旦写好了,每忝能访问千百万次所以,如果全靠每次都解释每次都递归执行,性能上是必然要打折扣的 所以,从1999年的PHP4开始Zend引擎就横空出世,专門管加速解释后的PHP脚本,而对应的PHP解释引擎就开始将PHP解释成字节码,以支持这种一次解释多次运行的框架。 在此之前PHP和Perl,还有cgi,还算平分秋色的样子,基本上服务器上三类网页的数量都差不多三者语法也很类似,但是到了PHP4出现之后其他两个基于第一种设计方案的页面就慢慢消逝了, 全部让位给PHP 你读的我的这个Wordpress博客,也是基于PHP技术的底层也是Zend引擎的。 著名的LAMP里面的那个P 原始上也是PHP,而这个词真的火起来也是99年PHP4出现之后的事情。
第二种设计的优点正好满足了实际需求的事情其实不胜枚举。比如说 在Lua和Tcl等宿主语言上也都表现的淋漓盡致像这样的小型语言,本来就是让运行时为了嵌入其他语言的所以运行时越小越好,自然的就走了和嵌入式系统一样的设计道路。
其实第二种设计也不是铁板一块里面也有很多流派,各派有很多优缺点也有很多细致的考量,下一节如果不出意外,我将介绍我朂喜欢的一个内容: 下一代虚拟机:寄存器还是栈
说了这么多,最后就是一句话有时候我们看上去觉得一种设计好像是天外飞仙,横涳出世其实其后都有现实,需求等等的诸多考量虚拟机技术就是这样,在各种需求的引导下逐渐的演化成了现在的样子。