Turbo Pascal的进化史

2018-09-14 12:36:14


前言

关于Pascal

Pascal已经离我们很远了,将要退出NOI系列的支持。实际上,现在的OIer已经很少用Pascal了,常规的都是C++。你随便打开洛谷的评测队列或题解就能发现。但是Pascal更容易学习和阅读,尽管标准提供的功能并不丰富。另外Pascal也是当时的OIer最早学的语言(某些地区可能是BASIC)。

关于Turbo Pascal

Turbo Pascal是我最早用的IDE,在Windows的时代,它已经相当古老了。现在虽然有Free Pascal,但是其稳定性和编译速度比不上TP。TP可以在CP/M-80,CP/M-86和DOS下使用,我只研究DOS的版本,毕竟CP/M更加古老,也更难模拟。然而,在我用的x86_64平台下,根本不能运行16位的TP,所以我需要PCem——一个PC模拟器。

Borland公司的TP比当时常见的编译器便宜,IDE的使用很大程度上简化了编译程序的过程,以及更加强大的语言。宣称只需一张软盘和一台PC,就可以编写和调试大型程序。Turbo则点出了TP编译和目标代码的高效。因此TP受到了很大的欢迎,也使Pascal流行一时,成为DOS下著名的软件。Borland还推出TP的教程和代码工具箱等相关产品。

TP有一个短暂的后继版本Borland Pascal,也有一个短暂的TP for Windows,以及更加著名的Delphi。Delphi用于开发win32图形界面程序,使用可视化、拖放式操作(与信息技术用的VB6类似),但已没有当时的欢迎了,现在早已销声匿迹。Borland公司也已经被Embarcadero收购。有一个开源的替代品Lazarus,基于Free Pascal,也可以了解一下。

版本比较

1.0(1983)

官方介绍

注:在Delphi的时代,Borland免费提供过时的TP1.0、3.0、5.5,以及当时的介绍。

原文http://edn.embarcadero.com/article/20693,翻译有删改。

官方下载:http://altd.embarcadero.com/download/museum/tp1.zip

发布日期:1983/11/20

价格:$49.95+5(运费),在当时相对便宜。

系统要求:Intel 8086和Zilog Z-80微处理器,64KB内存!

TP1在单张软盘上发行,共10个文件,131,297字节。TURBO.COM(包含带有编译器的IDE,Wordstar风格的编辑器和在内存中运行的系统)大小只有33,280字节。TP1是相当小的。

使用

TP1还不支持目录操作,只能使用当前目录。除此以外,TP1和2基本上一样。实际上,TP2的手册附录P前面的就是TP1的手册,据说更早时TP2直接提供TP1的手册加上附录P。

关于用户界面的使用参见TP2。

DOS功能调用

在早期的TP中,没有直接提供DOS接口,使用MsDos过程调用DOS功能。下面的代码提供了一种获取时间的方法,可以放到一个包含文件中。其他功能调用参见DOS参考手册,或者用较新的TP。

type
  regfile=record
    AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags:integer;
  end;
  timeRec=record
    h,m,s,s100:integer;
  end;

procedure getTime(var t:timeRec);
var
  reg:regfile;
begin
  reg.AX:=$2C shl 8;
  MsDos(reg);
  t.h:=reg.CX shr 8;
  t.m:=reg.CX and 255;
  t.s:=reg.DX shr 8;
  t.s100:=reg.DX and 255;
end;

2.0(1984)

使用

用户界面很奇怪,但还是可以用的。在主菜单,按下高亮的字母可以选择对应的功能。如果要打开或新建一个文件,需要:

  1. L切换到需要的驱动器
  2. A改变当前目录
  3. W输入文件名
  4. E进入编辑模式
  5. ^KD退出编辑模式
  6. S保存
  7. C编译
  8. R运行

以下为运行一个类似屏保的程序ART.PAS的效果:

默认情况下,TP2把程序编译到内存中,因此可以直接运行。如果需要生成可执行文件,需要修改编译选项为Com-file。编译ART.PAS生成的ART.COM只有11,518字节。

递归测试和“调试”

编辑模式没有代码高亮功能。默认情况下,递归了8,127层,然后运行时错误FF,并给出了PC。如果编译到内存,会自动定位RE位置。如果编译到文件,只要记录下PC,然后在选项中选择F即可。当然,TP2还没有调试功能,只有简单的找到RE位置的功能。

program recursion;
var
  maxdep:integer;

procedure recur(x:integer);
begin
  if x<=maxdep then
  begin
    writeln(x);
    recur(x+1);
  end;
end;

begin
  writeln('hello, world');
  read(maxdep);
  recur(1);
end.

覆盖功能(overlay)

覆盖功能可以把一个程序的多个功能模块加载到内存中的同一个位置,以扩大程序的规模。只要在过程或函数声明前加入overlay关键字。

同一区域内的覆盖函数或过程不能相互调用,也不能使用forward定义,不能递归调用。总之在一个时刻,一个覆盖组内只能运行一个覆盖函数或过程。

注意,覆盖文件保存在磁盘上,因此频繁调用覆盖会降低效率,应该权衡使用这个功能。下面用文档中的插图来形象化理解。

3.0(1985~1986)

官方介绍(3.02)

官方下载:http://altd.embarcadero.com/download/museum/tp302.zip

发布日期:1986/9/17

价格: $99.95(包含8087和BCD);CP/M-80版本(不包含8087和BCD):$ 69.95

系统要求(16位):128KB内存,MS-DOS 2.0。

速度改进

2.0和3.0都提供了一个很大的示例CALC.PAS,有30+KB。这是一个简单的表处理程序,有较高的实用价值,提供带注释的源代码,这是TP当时推销的一个亮点。下面是一个估测比较(IBM AT 286/6),我不会告诉你我是用手表计时的

加载时间 编译到内存 编译到文件
2.0 2s 8s 10s
3.0 <1s 4s 4s

可以看出有不少改进。

Turbo-BCD

Turbo-BCD利用BCD来存储浮点数,以避免十进制的误差。例如,十进制0.1在二进制下无法精确表示,但是可以用十进制浮点数(BCD)精确表示。

可以用BCD.PAS来演示Turbo-BCD的优势。使用TURBO.COM编译的结果:

使用TURBOBCD.COM:

Turbo-87

需要8087协处理器,我在没有x87的IBM AT(286)上运行,结果是一个无效的浮点数。因此,必须在有数学协处理器的PC上测试。由于PCem暂时没有独立的模拟x87的开关,所以我用486DX,也就是最早内置x87的CPU了。

演示Turbo-87的有HILB.PAS和TEST.PAS,下面使用TEST.PAS,可以比较速度和精度。我包含了我自己写的计时库TIMER.PAS。使用TURBO.COM:

使用TURBO-87.COM:

使用以下代码的结果为1.644903548804433,与Turbo-87基本一致:

#include <iostream>
#include <limits>
using namespace std;
int main()
{
    int n;
    cin >> n;
    double ans = .0;
    for (int i = 1; i <= n; i++)
        ans += 1.0 / (i * i);
    cout.precision(numeric_limits<double>::digits10);
    cout << fixed << ans << endl;
    return 0;
}

4.0(1987)

使用

TP4已经有菜单了,看起来比前几个版本好多了。切换到菜单可以用Alt+高亮字母。

编译到文件生成的ARTY4.EXE有24,384字节,比2.0大了很多。

升级早期程序

UPGRADE.EXE用于完成这一功能,我用3.0的CALC.PAS试了,原始文件被重命名为CALC.3TP。不过编译升级后的文件也用了4s,没有手册所说的速度提升。不过,MCALC.PAS提供的MicroCalc比以前的高级多了。不但增加了颜色,而且再次编译很快,因为采用了MAKE.EXE。

单元和Make

单元是4.0最大的改进,单元中可以提供各种接口。这样就可以把一个很大的程序分成一些单元,可以分别编写和调试,最后连接成最终的程序。当只修改部分文件时,利用Make功能,只需要重新编译修改部分再连接即可。而Build功能则重新编译所有的文件。

单元的结构如下:

unit unitname;
interface
{ uses <list of units>; Optional}
{ public declarations }
implementation
{ private declarations }
{ procedures and functions}
begin
{ initialization code}
end.

实际上,根据上述结构,单元与C中的头文件、实现文件类似。其中interfaceimplementation之间的部分类似于头文件,而implementation后的部分实现接口。TP提供的各种接口也通过单元来提供,使用uses来包含其他单元。

新的数据类型

TP4提供了新的整数类型longint,shortint,word,如果有x87协处理器,还可以使用single,double,extendedcomp。在此之前,TP只提供了16位整数,现在最多可以用64位整数(即comp)。

TP中的64位整数comp:很多用过Pascal的OIer都不知道TP也是有64位整数的,comp的范围为 $-2^{63}+1\dots2^{63}-1$ 。虽然comp是浮点数类型,但是只能保存整数,而且范围与标准的64位整数相差1。comp需要x87协处理器才能使用。

5.0(1988)

安装

这是第一个需要安装的版本,我用的是2张720KB软盘的版本。INSTALL.EXE用于安装,安装过程不仅包括复制文件,还需要解压缩.ARC。

使用

用户界面与4.0相差较大,蓝色占了大部分面积,菜单也更加丰富了,更接近现代的版本。现在Output用经典快捷键Alt+F5,全屏显示。相比4.0主要增加了调试功能。另外,运行MicroCalc时显示的可用内存增加了很多(都在内存中运行)。

源代码级别的调试

TP5终于在IDE中支持基本的调试功能了,使用也很简单。用户手册中提供了以下例程:

{$D+,L+} { Just to be sure complete debug information is being generated }
{$R-}                            { Just to be sure range-checking is off }
program RangeTest;
var
  List : array[1 .. 10] of integer;
  Indx : integer;
begin
  for Indx := 1 to 10 do
    List[Indx] := Indx;
  Indx := 0;
  while (Indx < 11) do
  begin
    Indx := Indx + 1;
    if List[Indx] > 0 then
      List[Indx] := -List [Indx]
  end;
  for Indx := 1 to 10 do
    Writeln(List[Indx])
end.

这段程序是死循环的,原因并不清晰:因为List[11]的位置就是Indx,因此接下来把Indx设置为-11,造成了死循环。然而,如果不知道TP的内存分配机制,可能很难想到这个错误。

而使用调试功能,很容易发现这个问题。这要把List和Indx加入Watch,然后F8单步执行,当Indx=11的时候,就显示了上述行为的发生。如果打开范围检查({ $R+})呢?在修改前先停止调试,即Run-Program reset。这时就报告错误201了,这个错误相信大家都很熟悉。不过需要注意的是,TP5默认不开启范围检查,需要手动改选项或加上编译开关{$ R+}。

EMS扩展内存

先让我来解释一下EMS是什么。在8086/8088中,寻址空间为 $2^{20}$ =1MB,其中低640KB可以被程序直接访问(常规内存),而剩下的384KB则用于硬件。而到了IBM AT/286时代,尽管寻址空间达到了16MB,但是只支持在保护模式下访问,没有任何DOS程序可以在那里运行。

当时的解决方案之一是内存盘,这个相信大家应该不陌生,但是内存盘不能用来运行程序。另一种方法就是扩展内存,利用384KB的一部分来作为“窗口”来使用高内存区。其中一种就是Lotus/Intel/Microsoft Expanded Memory Specification (EMS),TP5支持3.2+的版本,在扩展内存中可以存放覆盖文件,并将扩展内存用于内置代码编辑器。可以用EMS.PAS来检测EMS是否可用。

5.5(1989)

官方介绍

官方下载:http://altd.embarcadero.com/download/museum/tp55.zip

发布日期:1989/5/2

TOUR

5.5包含了一个TOUR.EXE——一个在线的介绍工具,可以让新手了解IDE的使用,大约需要15分钟完成。(从这里开始换到了学校的Windows 7)

面向对象

5.5最主要的改进就是全面支持面向对象程序设计(OOP),当然TP主要借鉴了C++。其实从某种角度而言,对象就是带有过程和函数的记录,在TP中用关键字object代替record

方法重载

type
  Circle = Object(Point)
    Radius : Integer;
    procedure Init(InitX, InitY : Integer; InitRadius : Integer);
    procedure Show;
    procedure Hide;
    procedure Expand(ExpandBy : Integer);
    procedure MoveTo(NewX, NewY : Integer);
    procedure Contract (ContractBy : Integer);
  and;
procedure Circle.Init(InitX, InitY : Integer; InitRadius : Integer);
begin
  Point.Init(InitX, InitY);
  Radius := InitRadius;
and;
procedure Circle. Show;
begin
  Visible := True;
  Graph.Circle(X, Y, Radius);
and;
procedure Circle. Hide;
var
  TempColor : Word;
begin
  TempColor := Graph.GetColor;
  Graph.SetColor(GetBkColor);
  Visible := False;
  Graph.Circle(X, Y, Radius);
  Graph.SetColor(TempColor);
end;
procedure Circle.Expand(ExpandBy : Integer);
begin
  Hide;
  Radius := Radius + ExpandBy;
  if Radius < 0 then Radius := 0;
Show;
end;
procedure Circle. Contract (ContractBy : Integer);
begin
  Expand(-ContractBy);
end;
procedure Circle.MoveTo(NewX, NewY : Integer);
begin
  Hide;
  X := NewX;
  Y := NewY;
  Show;
end;

重载方法只要定义完全相同的方法,并改变内容。在上例中,用Point.Init来调用Point的方法。

构造和析构

TP使用关键字constructor修饰构造过程,用destructor修饰析构过程,建议命名为InitDone

Point = object(Location)
  Visible : Boolean;
  Next : PointPtr;
  constructor Init(InitX, InitY : Integer);
  destructor Done; virtual;
  procedure Show; virtual;
  procedure Hide; virtual;
  function IsVisible : Boolean;
  procedure MoveTo(NewX, NewY : Integer);
  procedure Drag (DragBy : Integer); virtual;
end;

面向对象的Calc

历史悠久的MicroCalc被重命名为TurboCalc,并且用面向对象重写。

6.0(1990)

IDE

新的IDE支持多窗口编辑,并支持窗口重叠、并列等排列(Windows 2.0)。支持鼠标,不过我自己安装了CuteMouse才能用。

Turbo Vision

Turbo Vision是一个面向对象的应用程序框架,用于开发图形界面程序,风格与IDE相同。当然,其实还是基于文本界面的,只是提供了窗口、菜单、状态栏、对话框等元素的实现。上图是一个示例,缺点是很费内存,右下角显示的数字就是可用内存。应该在IDE外运行时可用空间将增加。

退出IDE后,可以自由打开文件,但这个程序只能查看文件,不能修改。另外提供了4个工具:15数码、日历、ASCII表和计算器。

另外还有智能帮助功能,只要按下F1即可。

注意这些都是用Turbo Vision实现的,而不是TP自带的。

面向对象的改进

private带来了私有的字段和方法,在上一个版本中没有提供支持。但是,与C++中的class不同的是,默认的仍然为public(当然没有这个关键字),类似于struct(POD)。

type
  NewObject = object(ancestor)
    fields; { these are public }
    methods; { these are public }
  private
    fields; { these are private }
    methods; { these are private }
  end;

TPTOUR

这个版本的TOUR比5.5要高级多了,在鼠标使用方面有点像Windows 3.1的教程。不过这个教程还是偏向于使用键盘而不是鼠标,有时经常会出现警告:请使用键盘而不是鼠标。那为什么要支持鼠标呢?

7.0(1992)

IDE

除了提供传统的实模式(8086)下的TURBO.EXE之外,还提供了保护模式下的TPX.EXE。要运行保护模式下的IDE,必须具有286和至少2MB内存。

两个IDE有一些区别,内存占用不同,并且TPX不再提供编译到内存功能,只能编译到文件。

语法高亮

7.0支持默认关键字高亮为白色,注释为灰色,汇编为绿色,其余符号为黄色。当然这个设置可以在菜单中手动修改,可以修改到接近Free Pascal的高亮,但是不识别编译开关。

撤销功能

撤销和恢复功能,与现代编辑器相同,可持久化……

右键菜单

这个在现在看来也很基本……

总结与展望

现在研究PC及相关软件的历史要比真正的历史容易多了,因为有相对齐全的资料,甚至有模拟器,可以很好还原当时的情况。模拟器和虚拟机还是有很多区别的,一般而言模拟器可以模拟不同的体系和硬件,但很慢,因此只能模拟至少十多年前的硬件(差不多是XP时代)。希望本文能引起大家对软件史乃至技术史的兴趣。

如果你对这个主题比较感兴趣,可以去研究接下来的Delphi,虽然比较乱;也可以试试Photoshop之类的历史。可以延伸阅读A History of the Personal Computer(电子书)。磁盘镜像:https://pan.baidu.com/s/1UdO-A7Sixb6LFNxjBz1iMQ 提取码: pvm3。或https://1drv.ms/u/s!AvZtXVYz4gw7gbpZvapwkZ6Hc1poAA

本文的未删减版在我的博客上发表:https://zhzh2001.coding.me/2017/08/12/history-of-turbo-pascal-v2/,这里根据需要删减了很多内容。因此,文中的图片也来自博客。引用基本上翻译自TP各版本的手册,而使用截图都是在Windows 10/7上用PCem v12得到的。

在修改过程中,感谢@ComeIntoPower和我的一些同学的意见和帮助。下一步计划介绍我的一些更加奇怪的研究,比如在很小的空间或较短的时间内排序 $10^8$ 个浮点数(新挑战),计算 $10^{11}$ 内质数表的哈希值,计算 $10^{18}!$ 的近似值(LOJ#6242),甚至关于视频编码的一些研究。敬请期待!

参考文献