存储程序计算机传奇与机器语言汇编语言高级语言(纸带上的每一行孔代表一个字符穿孔纸带上的当前位置用5个孔位的编码表示一个数字或字母)

存储程序计算机传奇与机器语言汇编语言高级语言(纸带上的每一行孔代表一个字符穿孔纸带上的当前位置用5个孔位的编码表示一个数字或字母)

众所周知,现代计算机产生的数学基础是数理逻辑,物理基础是开关电路,计算机实际上是自动执行计算和逻辑操作的开关电路系统,且现在的绝大多数计算机都是冯·诺依曼存储程序体系结构的计算机。

1 存储程序计算机传奇

1936年,阿兰·麦席森·图灵(Alan Mathison Turing, 1912~1954)为证明“不可计算数”的存在而提出图灵机模型。这一“思想实验”抓住了数理逻辑和抽象符号处理的本质:一台仅能处理0和1二元符号的机械设备,就能够模拟任意计算过程——这就是现代计算机的概念模型。

1938年,克劳德·艾尔伍德·香农(Claude Elwood Shannon, 1916~2001)开创了开关电路理论,在数理逻辑和物理实现之间架起了桥梁。

1940年,针对当时纷纷出现的计算机特别是模拟计算机,诺伯特·维纳(Norbert Wiener, 1894~1964)提出计算机设计的五原则:(1)计算机负责运算的中心部件不应是模拟式,而应是数字式;(2)开关装置应该采用电子元件;(3)采用二进制,而不是十进制;(4)运算和逻辑判断都由机器完成,中间应该没有人的干预;(5)内部要有存贮数据的装置,支持快速读写。

1945年6月,约翰·冯·诺依曼(John von Neumann, 1903~1957)公开了离散变量自动电子计算机(Electronic Discrete Variable Automatic Computer, EDVAC)逻辑设计报告草案,提出了存储和计算分离的存储程序结构(即冯·诺伊曼体系结构),这实际是图灵机的一种通用物理实现方案,同一套硬件(负责逻辑和计算的通用中央处理器)可以执行多种功能(存放在存储器中的程序)。

冯·诺伊曼体系结构的核心概念是计算和存储分离,数据和指令一起存储(与通用图灵机使用的“一条纸带”是等价的)。

冯·诺依曼结构:数据和代码放在一起(整体放到存储器,分区存储)。

哈佛结构:数据和代码分开存在。

1946年,宾夕法尼亚大学研制成功第一台电子数值积分计算机(Electronic Numerical Integrator And Computer, ENIAC)。

实际上,真正的第一台电子计算机是1939年的阿塔纳索夫-贝瑞计算机( Atanasoff–Berry Computer,简称ABC计算机)。ENIAC是第二台电子计算机和第一台通用计算机 。

ENIAC实际上是用约1.8万个“电开关”(电子管)搭建的大型开关电路系统,用电子管做为逻辑元件,尚未采用冯·诺伊曼体系结构,没有存储器,所以其程序并不是内置的,而是通过开关和重新调整电缆的连接方式来改变程序。ENIAC也并没有采用二进制而是十进制,其输入输出设备采用IBM的穿孔卡片机。

打孔卡是早期计算机的信息输入设备,通常可以储存80列数据。打孔卡盛行于20世纪70年代中期。我们应当注意的是:打孔卡比计算机更在出现。其历史可以追溯到1725年的纺织品行业,用于机械化的织布机。和打孔卡一样,也是纺织行业的机械化织布机率先使用打孔纸带。在计算机上,打孔纸带即可用于数据输入,也可用于存储输出数据。纸带带上的每一行孔代表一个字符,穿孔纸带上的当前位置用5个孔位的编码表示一个数字或字母。

磁带首次用于数据存储是在1951年。磁带设备被称为UNISERVO,它是UNIVAC I型计算机的主要输入/输出设备。UNISERVO的有效传输效率大约是每秒7200个字符。磁带装置是金属,全长1200英尺(365米),因此非常重。

1956年9月13日,IBM305RAMAC计算机问世。随之一起诞生的是世界上第一款硬盘——IBM Model 350硬盘,它由50块24英寸磁盘构成,总容量为5百万个字符(不到5MB)。

EDSAC (Electronic Delay Storage Automatic Calculator,电子延迟存储自动计算机)是世界上首次实现存储程序计算机。EDSAC由英国剑桥大学威尔克斯 ( Maurice Vincent Wilkes )领导、设计和制造的,并于1949年投入运行。它使用了水银延迟线作存储器,利用穿孔纸带输入和电传打字机输出。EDSAC 是第一台采用冯·诺依曼体系结构的计算机。威尔克斯后来摘取了1967年度计算机世界最高奖——“图灵奖 ”。

EDSAC的内存槽5英尺长,内含32个内存位置。使用水银延迟线作存储器,分布在32个槽中,每个槽5英尺长,里面包含32个内存位置,共1024个位置。建造时只实现了一半,512个字,第二组于1952年添加。1952年增加一个磁带存储,但实际使用中不能良好工作。阴极射线管输入采用5路的穿孔纸带,使用电子纸带读入机,速度为每秒个字符,1949年10月改进为每秒16字符,1950年使用光电阅读器,达到50字符每秒。输出使用电传打字机,速度字符每秒,1951年添加一个16字符每秒的纸带打孔机。另外,EDSAC可以外接阴极射线管(CRT),可以用来观察寄存器的值。指令集由运算器(ALU)、控制器来实现。

2 计算机指令集与机器语言

在一台冯诺依计算机中,最核心的就是CPU和内存,指令和数据都存入在内存当中,CPU每次取出一条指令,译码、执行,然后把结果写回内存。

指令集是对CPU的抽象,文件是对输入/输出设备的抽象,虚拟存储器是对程序存储的抽象,进程是对一个正在运行的程序的抽象,而虚拟机是对整个计算机(包括操作系统、处理器和程序)的抽象。

计算机能够执行的指令是直接由硬件完成的,与硬件设计有关。不同的硬件设计产生不同的指令系统。因此,不同类型的计算机所能执行的指令是不同的。

程序是一个有序指令的集合,用来完成一定的功能,解决某一特定问题。根据计算机的指令集就可以编写计算机能执行的程序。

因为不同的生产厂商生产的计算机有不同的机器语言(二进制指令集),所以,用机器语言编写的程序并不具有移植性。

3 由机器语言到汇编语言、高级语言

二进制代码形式的语言称为机器语言。早在计算机诞生之初,人们就是直接用机器语言编程。但是,这种在计算机看来十分明了的机器语言程序,在人看来却是一部“天书”。后来,人们又将3个二进制位合并在一起变成了八进制。再后为,为了与字节对应,又将4个二进制位合并在一起变成了十六进制。将机器语言写成八进制,或十六进制形式,要比二进制形式“好看”一些。

随着计算机应用不断扩大,程序需求量大增,编程工作量也越来越大,人们便产生了用符号代表机器指令的想法,设计出汇编语言(Assemble Language,又称符号语言)。比如,用ADD表示加法指令,用SUB表示减法指令,要比用二进制0\1序列“00111011”表示某一条指令直观得多。用汇编语言编写的程序称为汇编程序。将汇编程序送入计算机,由计算机自动地将汇编源程序翻译成计算机能够直接执行的二进制程序,而计算机的自动翻译功能是由专门的软件--汇编程序(Assembler,又称汇编器)完成的,当然,这个软件也是人们事先编写的,并安装在计算机系统中。一台计算机配上汇编程序就相当于人们“教会”计算机认识汇编语言了。再后来,人们又设计出反汇编程序,它能将机器语言程序反过来翻译成汇编语言程序。通过反汇编,人们就可以读懂安装在计算机中的可执行程序。

机器语言是一串二进制数字,将计算的步骤从指令表中查出对应的机器语言编码,再人工写成数列。而查表(指令表)的工作正是计算机所擅长的,指令表的二进制编码用助词符来表示,用汇编器来翻译,这就是汇编语言机制。

使用汇编语言减轻了不少人们的编程工作量,但是,汇编语言仍然十分原始,一条 汇编语句(也称为汇编指令)对应一条机器指令,易读性仍然很差。编制一个程序,哪怕只是用来完成简单计算任务的程序,通常需要成百上千条汇编指令。不仅编程效率低,程序不易调试,而且容易出错。更为麻烦的是,这种语言是完全按照计算机硬件设计的,不同各类的计算机都有自己特有的机器语言和汇编语言,一种类型的机器无法识别另一种类型机器的机器语言,所以,汇编源程序缺乏可移植性。

人们都机器语言和汇编语言归属为低级语言。

汇编语言的出现具有划时代的意义,它启发人们,可以设计出更好用的语言,只需要通过翻译器,将新语言的源程序翻译成机器语言程序。于是,人们期待的脱离计算机机种的高级程序设计语言便陆续被设计出来。

高级语言引入变量、数组以及结构化程序思想(分支、循环等控制结构)、函数(子程序)以及接近数学语言的表达式等语法成分,用接近英语口语的语句描述处理步骤(如if...then...else...),不仅容易理解和记忆,而且一条语句的处理能力相当于几条、几十条、甚至几百条 汇编指令,大大提高了编程效率。

编程语言的进化在于抽象化,“赋予名称”是抽象化的重要部分。抽象程序高的语言不必描述详细过程。

高级语言的变量声明,其实就对应着内存的一个存储单元,流程控制语句对应着CPU的跳转指令,函数调用对应着内存的栈帧。

指令与数据存储的约定与机器语言、汇编语言、高级语言对比:

机器语言、汇编语言、高级语言完成一个简单程序的对比:

4 高级语言的结构化程序思想

20世纪60年代初,在提倡规则让读写程序更轻松的时代潮流中,结构化程序设计应运而生。时至今日,大家对if、while这样的语句早已习以为常。结构化程序设计的初衷正是通过导入这些语句使代码结构理解变得简单。

结构化程式设计(Structured programming),一种编程典范。它采用子程序、程式码区块(block structures)、for循环以及while循环等结构,来取代传统的goto。希望借此来改善计算机程序的明晰性、品质以及开发时间,并且避免写出面条式代码。

指令顺序存放,控制器顺序读取,但可以根据设定的条件跳转读取,向前跳转可以实现选择结构,向后跳转可以实现重复(循环)结构。

程序是命令的有序集合,命令执行的顺序即程序的结构。一个程序的功能不仅取决于所选用的命令,还决定于命令执行的顺序。结构化程序设计把程序的结构限制为顺序、分支和循环3种基本控制结构。

5 高级语言的函数编程机制

函数是编程语言的一种很重要的编程机制,用于将一段计算包装起来,然后可以多次调用。 函数把解决一个小问题的代码构建成块,可以反复调用,在逻辑上更易于阅读和理解,也可以减少代码的重复使用或冗余。另外,函数还可以组合成模块,然后在其实模块中可以引入包含有需要的函数的模块,并通过模块名和函数名来调用。

给函数添加参数的过程称为泛化(generalization)。因为它会让函数变得更通用。如你想计算一个正方形的面积,如果不提供参数,只能是算某一个具体边长的正方形的面积,如果你将边长做为一个变量len,并提供给函数做为参数,那在这个函数的新版本中,你就可以计算任意大小的正方形的面积。

函数为我们提供了在程序设计时的抽象机制,可以首先解决做什么的问题,设计出程序的结构,通过函数机制做出接口,具体的实现可以逐步细化。

一个单元就是一个模块。在任何一个单元中,分为接口和实现两个部分。我们要用这个模块,只需要理解接口中的调用函数用法即可。

函数就是一个功能部件,其头部定义了它的接口,描述了函数的名字及其对参数的要求。使用者只需要考虑函数的功能是否满足实际需要,还要保证调用式符合函数头部的要求,并不需要知道函数实现的任何具体细节。

函数接口的作用。程序员各自开发各自的模块,只需要约定好互相调用的接口即可。

6 高级语言的抽象机制

在程序开发的实践中人们逐渐认识到,仅有计算层面的抽象机制和抽象定义还不够,还需要考虑数据层面的抽象。能围绕一类数据建立程序组件,将该类数据的具体表示和相关操作的实现包装成一个整体,也是组织复杂程序的一种有效技术,可以用于开发出各种有用的程序模块。要把这种围绕着一类数据对象构造的模块做成数据抽象,同样需要区分模块的接口和实现。模块接口提供使用它提供的功能所需的所有信息,但不涉及具体实现细节。另一方面,模块实现者则要通过模块内部的一套数据定义和函数(过程)定义,实现模块接口的所有功能,从形式上和实际效果上满足模块接口的要求。

C++包含一组内置的数据类型,例如字符型、整型和浮点型。每一种数据类型都有一个唯一的允许聚会范围及一组允许的操作和函数。例如,浮点型所允许的正负取值范围与整型所允许的取值范围就是不同的。平方根函数sqrt()既适用于整型数据,也适用于浮点型数据。像length()这样的字符串处理函数就不能应用于整型或浮点型数据。

要使用一个整型或浮点型变量,不需要知道负数是怎样存储的或者用多少位去存储一个数据值的这些细节。对于一个特定的数据类型,只需要了解哪些函数和操作可以使用及这个数据类型的合法取值范围是什么。换句话说,数据类型的实现细节对程序员是隐藏的,称为数据抽象。

在冯·诺依曼体系结构中,程序代码和数据都是以二进制存储的,因此,对计算机系统和硬件本身而言,数据类型的概念其实是不存在的。机器指令和汇编语言中,数据对象是用二进制数表示的,内存里存的都是二进制,对于内存里存的内容,可以说 “你认为它是什么,它就是什么”。在高级语言中,为了有效地组织数据,规范数据的使用,提高程序的可读性,方便用户使用,引入了整型,实型等基本数据类型。不同的高级语言会定义不同的基本数据类型。编程时只需知道如何使用这些类型的变量(如何声明,能执行哪些运算等),而不必了解变量的内部数据表示形式和操作的具体实现。

数据的存储约定:约定数据的存储空间大小(数据类型)、存储空间地址(变量名称)、数据编码和解码方式(数据类型)、字节排序方式(大头方式还是小头方式)。

尽管构造数据类型机制使得某些比较复杂的数据对象可以作为某种类型的变量直接处理,但是这些类型的表示细节对外是可见的,没有相应的保护机制,因而在使用中会带来许多问题。例如,用户可在一个模块中随意修改该类型变量的某个成分,而这种修改对处理该数据对象的其他模块又会产生间接的影响,这对于一个由多人合作完成的大型软件系统的开发是很不利的。于是又出现了“信息隐藏”和抽象数据类型的概念。

所谓抽象数据类型(Abstract Data Type,ADT)是指这样一种数据类型,它不再单纯是一组值的集合,还包括作用在值集上的操作的集合,即在构造数据类型的基础上增加了对数据的操作,且类型的表示细节及操作的实现细节对外是不可见得。之所以说它是抽象的,是因为外部只知道它做什么,而不知道它如何做,更不知道数据的内部表示细节。这样,即使改变数据的表示和操作的实现,也不会影响程序的其他部分。抽象数据类型可达到更好的信息隐藏效果,因为它使程序不依赖于数据结构的具体实现方法,只要提供相同的操作,换用其他方法实现时,程序无需修改,这个特征对于系统的维护很有利。

C++中的类(Class)是抽象数据类型的一种具体实现,也是面向对象(Object-Oriented)程序设计语言中的一个重要概念。从结构体过渡到类是顺其自然的事情,但是不能将C++看成是带类的C,因为它带来的是思考和解决问题角度的转变。不同于面向过程的程序设计,在面向对象程序设计中,程序员面对的不再是一个个函数和变量,而是一个个对象。每个对象包含两个部分:数据和方法,数据用来保存对象的属性,而方法用来完成对数据的操作。对象与对象之间是通过消息进行通信的。

7 面向过程和面向对象的编程语言

典型的商用程序通常有几百甚至上千行代码。为了降低编写如此大规模代码的复杂度,程序员必须将其分解为较小、较简单的模块,例如函数和类。函数和类是构造C++程序的基本模块。

函数是用函数名来调用执行的具有特定功能的语句块。在C++的标准库中有很多固有的、预先定义的库函数。

在函数内部定义的变量是auto(自动)存储类型。每次进入函数(包括main())时,都为每个auto存储类型的变量重新分配内存空间;函数结束时,将分配的内存空间释放,存储在auto变量中的任何数值都将丢失。这样的变量称为局部变量,只能在定义它的函数内部访问。如果没有指定变量的存储类型,那么变量的存储类型,那么变量的存储类型就默认为auto。

和auto变量一样,static变量对定义它们的函数而言,也是局部的。但不同于auto变量的是,static变量只分配一次存储空间,因此即使函数结束后仍然保持其值不变。

变量的作用域指的是程序中可能访问到变量的部分,有两种作用域类型:块作用域和全局作用域。

一个语句块就是位于花括号{和}之内包含变量声明的一条或多条语句。在一个语句块内声明的变量只能在该语句块中被访问。

由于全局变量可以在任何函数中被访问,因此也可以被任何函数修改,这给程序的调试和维护带来困难。全局变量不是函数实参的替代品。严格来讲,除了局部变量之外,函数应该只能访问函数形参列表中指定的数据。

对象是程序一个组成部分,它知道如何执行特定的操作,知道如何去和程序的其它部分进行交互。一个对象包含一个或多个数据值及应用于这个对象的一些函数,其中这些数据值定义了这个对象的状态或者属性,和对象关联的函数表示可以对该对象做些什么及该对象是如何运作的。对象可以是任何事物,例如人、计算机,甚至是像银行帐户这样的无形的东西。

类同时定义了数据的类型和可作用于这些数据之上的操作。类把数据和函数包含在一起,成为一个整体。这个过程称为封装。

类的成员有私有成员和公有成员两种。用关键字private和public指定类的数据成员和成员函数的访问控制权限。被声明为private的数据成员只能被这个类的成员函数访问,其他的非成员函数无权访问,称为信息的隐藏。其目的是防止数据在这个类外被修改。

声明为public的数据成员可以在程序的任何部分被访问。公有成员函数称为类的公共接口。

以上分析解决问题所需步骤,用函数把这些步骤依次实现,属于面向过程的程序设计思路。

以上把构成问题的事务分解为各个对象,将数据(数据结构)和对数据的操作(方法或函数)抽象、封装到类和对象,属于面向对象的程序设计思路。建立对象目的,不是完成一个步骤,而是描述某个事务在解决整个问题步骤中的行为。面向对象技术是一种以对象为基础,以事件或消息来驱动对象执行处理的程序设计技术。它具有抽象性、封装性、继承性及多态性等。

C语言是面向过程的编程,它最重要的特点是函数,通过主main函数来调用各个子函数。程序运动的顺序都是程序员事先决定好的。

C++兼有面向过程与面向对象的编程,类是它的主要特点,在程序执行过程中,先由主main函数进入,定义一些类,根据需要执行类的成员函数,过程的概念被淡化了(实际上过程还是有的,就是主函数的那些语句),以类驱动程序运行,类就是对象,所以我们称之为面向对象程序设计。

形象地理解,面向过程就是把所有的功能全部在一个大的类里定义出来,当系统庞大时,功能多了,各种操作之间的调用关系也很复杂,当需要修改一个功能时就可能引发一连串的改动,使修改和维护成本很昂贵。面向对象是把功能以对象为基本单位进行分类,这就是面向对象程序设计所常说的类。当需要添加功能时,只需修改相应的类和极小的其他部分,即可达到目的。

面向对象在分析和解决问题的时候,将涉及的数据和数据的操作封装在类中,通过类可以创建对象。例如,可以构建一个Circle类(表示圆的类),它是半径不同的所有圆的一个抽象描述。通过类可以创建任意多个对象,这些对象之间是相互独立的。可以由Circle创建圆c1和圆c2,而c1和c2是彼此独立的对象。这样,对于事务的管控力度,就由某个具体的对象扩大到一个类,符合人们认识事务的习惯。

class Circle{private: double m_dRadius;public: void SetR(double r) { m_dRadius = r; } double GetArea() { return(3.14*m_Radius * m_dRadius); }};

面向过程与面向对象各有优势:

8 相关问题

8.1 机器语言为什么要用难以理解、难以记忆的二进制比特串来表示指令,而不用人们容易理解的符号来表示?

因为计算机是由逻辑电路组成的,而0、1正好对应于逻辑电路中的两种电平信号,可以直接翻译成控制信号,使计算机硬件实现比较容易。如果采用人比较容易理解的符号,如英文、中文或者数学符号,则计算机需要用硬件将这些符号翻译成控制信号,使硬件设计非常复杂,甚至无法实现。

8.2 为什么不直接用自然语言和计算机进行交互,而要设计专门的程序设计语言?

自然语言太复杂,而计算机本身(机器语言)的功能又非常简单,如果要将自然语言作为人机交互的工具,编译器的设计与实现必将非常的复杂。另外,自然语言太灵活,理解自然语言需要一些背景知识,否则会产生二义性,这也给计算机实现带来了很大的麻烦。

8.3 为什么不同类型的计算机可以运行同一个C++程序,而不同类型的计算机不能运行同一个汇编程序?

因为不同类型的计算机上有不同的C++编译器,可以将同一个C++程序编译成不同机器上的机器语言表示的目标程序。而汇编程序仅是机器语言的另一种表现形式。不同类型的计算机有不同的机器语言,也就有不同的汇编语言。

8.4 为什么在不同生产厂商生产的计算机上运行C++程序需要使用不同的编译器。

因为不同的生产厂商生产的计算机有不同的机器语言,所以需要不同的编译器将同样的C++程序翻译成不同的机器语言。

8.5 什么是源程序?什么是目标程序?为什么目标程序不能直接运行?

用某种程序设计语言写的程序称为源程序,源程序经过编译产生的机器语言的程序称为目标程序。因为程序可能用到了一些其他程序员写好的程序,没有这些工具程序的代码整个程序就无法运行,因此需要将目标程序和这些工具的目标程序链接在一起后才能运行。

8.6 试列举出高级语言的若干优点(相比与机器语言)。

首先高级语言更接近于自然语言和人们熟悉的数学表示,学起来比较方便。

其次高级语言功能比机器语言强。一般的机器语言只能支持整数加法、移位、比较等操作,而高级语言能执行复杂的算术表达式、关系表达式和逻辑表达式。高级语言可以使程序员在较高的抽象层次上考虑问题,编程序比较容易。

第三,高级语言具有相对的机器独立性,在一台机器上用高级语言编写的程序可以在另外一台不同厂商生产的计算机上运行,这使得程序有较好的可移植性,有利于代码重用。

-End-

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。

http://www.pinlue.com/style/images/nopic.gif

分享
评论
首页