返回入门教程首页 主页 | 开发工具 | 应用芯片 | 核心模块  
 
   
 

C 语言使你迷茫?
  Forth 可能是一个答案

原作者 Tom Napier 和 Eric Krieg

原文引自 www.Forth.org

曾经有个时期,人们不害怕从 IBM 购买计算机,和那时一样,现在人们当然也不害怕用C语言来编写嵌入式系统程序。如果还要再选择一个的话,那通常是汇编语言,尽管时尚正在转向Java 。只有很少的程序员使用Forth ,这种语言组合了汇编语言的速度、灵活性和紧缩性,又具有C语言的结构化和易读性。这些为数不多的程序员还发现了 Forth 能够提高编程的生产率。

在这篇文章中,我们希望(再一次)介绍 Forth 。你将会惊异于不需要复杂的工具就可以如此之快和交互式地编写和测试嵌入式程序。

编写程序的第一步是设计程序行为的细节。有些人画流程图,有些人用程序设计性语言(PDL),通过与英语类似的方式描述操作的序列和测试条件。完成这些之后,设计就被分成模块,每一块都被转成可执行的代码,全部的事情就是编译、连接、测试,这个迭代的过程可能会持续几个月。

如果PDL能够直接执行,就不需要把它翻译成另一种语言,那你该省去多少时间呀!如果你能交互式地测试每个程序模块,确认它能正确地工作,那不就更方便了吗?再假设有一种语言,它可以执行得和其它的语言一样快、只要1K字节的运行开销、符合 ANSI标准、可以扩展以满足你应用程序的特殊需要,经过一到两个星期的熟悉,你每天可以编写出三倍于你同伴的代码,那么你对这种语言感兴趣吗?如果是,请听如何用Forth来做到这些。

Forth 是什么?

从某种意义上说, Forth 不是一种语言,我们更应该把它看成一种为手头的任务编写应用语言的程序设计方法。你编写的大部分程序都是你工作的需要而不是编译器的需要。 Forth 支持你需要的任何操作和语法。

Forth 理解一定范围的原语字,它们处理全部正常的算术、逻辑和程序流操作,然而它也有一个确定的办法向语言加入新字。你可以确定哪些字能更好地描述你的应用,然后你用现有的字定义这些字。一但你定义了一个新字,这个字就变成了语言的一部分,可以用来定义其它的字。最高级别的字就是程序本身。

在 Forth 中,每个事物是一个字或者是一个数,它们彼此被空格分开。 Forth没有词法分析,语法也很少。没有操作符,没有函数,没有过程,没有子程序,甚至没有程序,只有字和数。

每个字告诉计算机去执行一个清晰的精巧定义的操作。定义一个字之后,你就可以把它作为一个独立的元素来测试。在你开始测试的时候你不需要完成全部程序,你可以在键盘上输入任何一个字,执行它,看结果是不是你所需要的。

Forth 也是它自己程序的符号调试器,所以测试一个 Forth 程序比测试其它语言的程序更快。你用增量化的方式编写 Forth 定义、测试定义。一但确认一个字能够工作,就可以把它加入到你的程序中;一但你定义了最高级别的字,就可以结束编程工作而不需要进一步的调试。

尽管 Forth 程序通常是自顶向下设计的,但是你需要自底向上编写,它要求你在使用一个字之前先定义它。但是实际上, Forth 程序通常是从两端向中间编写的。开始的时候,你知道所需要的程序顶级行为,你也知道与硬件交互的字必须做的事情,于是就有中间的工作需要完成。

你也可以先给某个功能一个名字,在定义之前使用它(如果你需要测试编译,你就给它一个空的名字)。一个程序的顶级字可以是一个无限循环,它用字 GET.FRONT.PANEL.INPUT 开始,后面是字 CHECK.USER.INPUT.LIMITS ,所以我们可以用 Forth 做 PDL 。当然,你在这里假设 CHECK.USER.INPUT.LIMITS 是存在的,最后你还得定义这个字的精确行为。

把程序分成可管理的自我描述的小块是每个优良程序的行为。所不同的是,在 Forth 中,最后的结果是一个可执行的程序,而不是另一个漫长过程的开始。

Forth 是编译器吗?

Forth 是编译的,但是它的用户界面是解释的。 Forth 维护一个它所知道的所有字的字典。每个定义由定义这个字的那些字的地址列表组成(为使代码更短,在32位或者更长地址的机器上可以使用16位的标记而不是实际的地址)。编译的过程就是把新的字和它们的定义加入到字典。

由于Forth把源程序中的每个字翻译成对应的地址, Forth 的编译器就很像是一个汇编器。图1是 Forth 编译器完整的流程图,如果把C语言编译器流程图同样地画出来,那会是一张 4' x 6' 的招贴海报。

图 1 Forth 编译器的完整的流程图

对于源程序中的每个字,这个循环都要执行一次

把Forth程序想像成全部是由子程序组成的,可能会对我们理解Forth系统有所帮助。由于每个字调用子程序,所以不需要 CALL 指令,它只是一个地址。在运行时,一个机器码片段读出下一个指令的地址,把当前程序计数器保存在返回栈上,执行这个调用。这个小小的开销对于每个字都要执行一次,导致了 Forth 程序比优化的汇编程序要慢。

Forth 是如何工作的?

执行一个无休止的子程序调用序列并不是一件很有效率的事情。幸运的是,大约有60个字是用机器码字义的。每个定义最终都是由这些“原语字”组合而成的,它们执行某些真正的工作。

原语定义了一个虚拟的Forth机器,要把 Forth 移植到一个新的系统上,只有这些原语字需要重写。某些Forth运行在 DOS 和 Windows上,而在嵌入式应用中,这些由机器码定义的原语字就是操作系统。

Forth 在堆栈上传递参数。在一个字执行之前,所需要的参数必须在堆栈上。而在执行之后,如果有任何的结果,也留在堆栈上。

这与大多数现代计算机语言的行为精确地一致,但是现代计算机语言的堆栈通常是隐藏起来的。在 Forth 中,程序员知道堆栈上的内容,并能够直接处理它们。例如,Forth 原语字 SWAP 交换堆栈上的两个元素。大多数语言保存未决的操作,当你写下 C = A + B ,编译器把“ = ”和“ + ”操作放到未决的表中直到读到表达式的结尾。然后它重写这个表达式为“取 A ,取 B ,加,存入 C ”。

Forth 消去了中间过程,在 Forth 中,你把同样的操作写成是 A @ B @ + C ! 。这里的 @ 和 ! 是 Forth “读取”和“存储”操作的缩写, + 非常奇怪地表示加法。

幸运的是,只有不多的 Forth 字用这种密码表示。大多数的 Forth 接受多达 31 个字符,大多数的标准字描述了它们的功能。好的 Forth 程序是自解释的,所以应该尽量使你定义的字成为自描述的。调试这个字的方法是打入它的输入参数,后随这个字。它立即执行,就好像 Forth 是一个解释器,允许你测试堆栈上的结果。

一个堆栈元素典型地有 32 位(有些Forth系统为16 位)并且是无类型的,它可以表示一个有或者无符号的整数、一个地址、一个单精度的浮点数或者是一个布尔标志。你需要对此保持跟踪。

Forth 的哲学是许可而不是禁止。如果你有一个好的原因把布尔值加到地址上,Forth不会阻止你。对于这些事情,Forth中没有任何东西能够阻止你把一个错误的项目放到堆栈上。 Forth 非常快速而高效,但是你自己也得睁大眼睛。

创建一个新的字义

Forth 中最重要的字可能是冒号,它把编译器从运行模式切换到编译模式并创建一个新的字典项。在冒号之后的第一个字是将要定义的字的名字,定义接着名字之后。逻辑上,定义被一个分号结束,这将编译一个返回指令并把编译器切换到运行模式。

于是,一个完整的 Forth 定义看起来像下面这样:

: MAGNITUDE (X Y—vector magnitude) DUP * SWAP DUP * + SQRT ;

括号中的表达式是堆栈说明,它提醒程序员这个字的输入输出参数是什么。 DUP(复制)操作产生栈顶元素的另一个拷贝, * 是单精度乘法,SQRT得到一个数的平方根。

作为 Forth 灵活性方面的一个例子,假设你需要 C 语言的 ++ 操作符, Forth 与之最近的等效是 + ! ,它把一个指定的数加到一个变量中。如果你定义:

: ++ 1 SWAP +! ;

则 ALPHA ++ 加 1 到变量 ALPHA 中, Forth 与 C 语言的唯一区别是 Forth 不允许 ALPHA++ ,但是C语言允许,因为 Forth 并不分析表达式,它会把 ALPHA++ 作为一个定义字。

程序结构

Forth 是高度结构化的,如果你确实需要,当然也有办法编译一个GOTO,但你通常使用 IF ELSE THEN BEGIN UNTIL WHILE REPEAT DO 和 LOOP 来控制程序的流程。这些字把条件跳转指令编译到定义中。

Forth 的IF检查栈顶标志,这个标志是许多 Forth 比较操作字中的一个留下的。比如我们希望比较堆栈上的两个数,如果相等就执行选择1,如果不等就执行选择2, Forth 的语法是这样的:

= IF 选择 -1 ELSE 选择 -2 THEN.

(我在自己的程序中使用 ENDIF代替THEN ,因为我觉得 THEN 对于 Basic 来说不合理。Forth 允许这样的个人化选择,尽管你的老板或者伙伴不允许)

常数 变量 和串

源文件中的一个数作为立即数编译。一个命名的常数在编译时存储一个值并在运行时把这个值放到堆栈上。命名一个变量则编译一个存储空间。引用一个变量则把它的地址放到堆栈上以准备读写。一个 Forth 串是一个变量区,它的第一个字节是串的长度。

由于变量指示了它的地址,所以能够在使用这个变量之前处理这个地址。例如,如果你使用的 Forth 系统没有 ARRAY 数组结构,你可以定义一个, Forth 能够指定定义字的新类型。另外,你可以"伪造"数组。 BETA 7 + C@ 读取数组中的第八个字节,这个数组开始于 BETA 变量的地址。

Forth 源程序的一个不足是我们不清楚一个字表示的是变量还是函数。有些人使用这样的传统:用连字符表示变量名而用小数点表示函数名。由于好的 Forth 代码与英语非常相似,在视觉上不需要分析一个整行就可以区别代码和说明,许多用户就用大写字母表示代码而用小写字母表示注释。

Forth 硬件

Forth 几乎在每个现存的或者曾经存在过的微处理器上都有实现,但是有些芯片更适合 Forth 系统运行。很明显,那些与图 2 的 Forth 虚拟机更接近的芯片能更好地运行 Forth 。 Forth 需要两个堆栈,那些支持一个以上堆栈的芯片将运行得更快。由于 Forth 只需要不多的寄存器,所以硅片上如果有寄存器也只能浪费。

图 2 Forth 虚拟机结构

Forth 虚拟机具有 HARVARD 体系结构,实现时通常用一个分离的寄存器保存栈顶元素

最小的 Forth 系统执行 16 位或者 32 位长的算术运算,所以 Forth 在 8 位芯片上运行较慢。历史上, Motorola微处理器比Intel 处理器更适合运行 Forth 。 MC6809 和 MC68X0 是理想的 Forth 8 位芯片。

由于 Forth 虚拟机相对简单,它可以在一个门阵列中实现。最早的努力是 Charles Moore , Forth 的发明人,指导 Harris公司于 1989 年推出了 RTX2000 。这个 10MIPS 的单芯片 Forth 引擎使用哈佛体系结构,并把参数栈和返回栈放到芯片上。不幸的是,这款芯片在商业上没有成功,现在只用于一些专用的市场,比如人造卫星。

使用 Forth

现在有各种级别的商业和公共的 Forth 版本。对于一个 8 位或者 16 位处理器的嵌入式应用,最方便方法的是在 PC 机上编写 Forth 程序,然后再把最终的代码传送到目标系统上,由于开发系统能够使用全部的 DOS 或者 Windows 能力,在最终的产品中只需要包含一个小的运行时间包和程序自身,字典只是在编译和调试程序时才需要,在最终的产品里可以被去除。

由于 Forth 程序趋向于编译大约每行10个字节,一个 2000 行的程序加上 4K 字节的运行时间文件可以很容易地放到 32K 字节的 PROM 中。如果目标系统运行串行口并能在 RAM 中执行,那我更喜欢在目标系统上编译和设计。尽管这意味着需要为字典和编译器找到存储器空间,但是它大大有助于硬件测试。我的许多硬件问题都是这样解决的:编写短的 Forth 程序来触发 I/O 位,再用一个示法器观察可疑的区域。

一个商业化的 Forth 系统有一个初始字典,它包含有 Forth 原语和需要实现 Forth 编译器的字。许多 Forth 系统有内建的编辑器,但是你可以使用任何方便的编译器。你也可能得到在操作系统上如 Windows 上开发 Forth 程序的库。对于一个使用单板PC的嵌入式应用, DOS 库就很有用了。

你也可以得到一个 Forth 扩展库,在程序需要它们的时候装入这些库。例如,简单的Forth只处理整数,而浮点是可选的。编写自己的库也很简单,我曾经用从 Delta Research 公司得到的 JForth 编写分析滤波器的程序。 JForth 支持 Windows 、下拉菜单、输入固件和控制参数滑块。然而,它不能处理复数,我在 20 分钟里就编写好了自己的浮点复数程序库。

在一个编写一系列相关控制仪器的团队中,应该有一个成员被指定编写硬件接口函数库,处理诸如用一致方式访问前台控制和显示等等工作。由于 Forth 允许程序员开发特殊的解决问题的方法,在一个大的项目中,你必须有好的文档和团队成员之间的紧密协调。使用 Forth 的公司应该为它们的程序员维护一个 Forth 内部扩展标准用于它们的产品和技术。

用 Forth 可以做什么?

简单地回答是:任何事情。其它的计算机语言限制你只能执行编译器的编写者认为你需要的操作。由于 Forth 是天然可扩展的,你可以做你需要的一切事情。如果所有的方案都不行,你还可以直接进入机器代码并创建你需要的任何数据结构。

JForth 甚至实现了C的结构,后者通常用于与主机的操作系统进行交互。我曾经需要一个结构,它写入30个命名的一维数组,作为一个单个的命名的两维数组。大家都说这用C实现起来很方便,但我从来就没有见到有人试着做过。

Forth 标准

自从 Charles Moore 1970 年发明 Forth 之后,出现了许多 Forth 标准和方言。Forth鼓励创新,所以总是有定制和改进它的倾向,就是在大家表面上接受了标准时也一样。我从1979 年开始从事古老的 FIG-Forth 编程,它已经非常古老甚至都无法改变。从那时开始,又有了 Forth-79 和 Forth-80 ,现在是一个 ANSI 的 Forth 标准( X3.215/1994 )。

如何比较 Forth 和 C ?

Forth 和C 都使得程序员能够在更高的级别上思维,并从较慢的汇编语言开发过程中解脱出来。 Forth 合理的文档顺序可以免去 C 语言中的原型说明。

全部的C 语言标准程序流控制( do if else while switch )在 Forth 中都存在,而且连名字都常常一致,所有重要的逻辑和算术操作也存在,条件比较、数组和联合都在 Forth 中支持, COSNTANT 替代了 #define , Forth 的直接堆栈处理省去了大多数的 C 语言 auto 变量。 Forth 字典的使用和 FORTGET 定义的能力比C语言的弱作用域操作能力更强大。你甚至可以比 C++ 更少痛苦地支持自己的数据类型。

Forth 假设你知道自己正在做的事情。它可以阻止你犯打字和结构不完整的错误,但是编译的错误代码手册通常只有一页,而不是整整一章。有人曾经说过:Forth 不能够标识语法错误,因为它不知道你准备使用什么语法。

在C语言中,你受到的保护更多。但即使如此,你还是必须做一些事情,如强制类型转换之类,来请求编译器帮助你做一些检查。

Forth 比 C 语言有这样一些优点:

•  开发环境更加简单。你不需要安装整个 Forth 开发包,因为 Forth 就是它自己的开发系统,在一个嵌入式应用中,也是它自己的操作环境。它提供了一个 OS 、源码编译器和你需要的全部调试程序,这些都放在一个360K字节的软盘上,结果是,你使用单一的工具集和单一的用户界面。你可以把这些与其它工具比较:一个编译器、OS、一个调试器,可能还有一个目标调试程序,它们都来自不同的开发商,并且不是为彼此协同工作而设计的;

•  当你购买 Forth 时,你通常可以得到全部开发环境的源代码。相反,你可以试着让 Borland 或者 Microsoft 给你想要的、向后兼容的 C 语言进行更强的类型检测、模糊控制逻辑或者不同的浮点实现;

•  常常能够在目标系统上开发 Forth 程序。在我的 C 程序合同中,我使用 Sun 工作站运行 MAKE 来编译和连接执行代码。然后在一个目标机器上,我在目标系统上电之前下载代码并测试它。如果我想做一个调整,它将花费一个小时来完成全部的过程。使用 Forth ,我可以通过目标机的串行口来打入一个新字,把参数放到堆栈上,然后调用它来检查这个字是否工作。我可以简单地结合新字以截获对老字的调用;

•  使用编译器的扩展能力可以使你进行任何时尚的编码而不需要切换语言。 Forth 从一开始就已经是面向对象的、“沙箱支持”和平台独立的。加入数据结构或者操作符重载几乎窒息了 C++ ,但在 Forth 中却没有任何问题;

•  你可以比C语言更容易地进入汇编语言,所有的数据结构都可以从汇编语言中访问;

•  目标测试更容易。你可以使用与代码中一样的命令来交互式地检查和处理数据,在C语言中做同样的事情需要更多的知识,它需要许多的键盘输入来控制调试器。你不需要一个目标操作系统, Forth 就是一个很好的 OS 。许多 Forth 支持多用户和多任务。因为每个任务有一个独立的参数栈和返回栈,所以任务的切换能够瞬时而高效。

•  Forth 在编译时分配存储器资源,它的执行时间是确定的。它不需要花费不确定的时间来整理存储器碎片。在一个实时OS 中,我选择不使用动态的存储器分配,但是如果你需要一些像 alloc() 和 free() 一类的操作,那也不是大问题,一页的代码足以实现这些功能。由于是基于堆栈的, Forth 可以用很少的开销进行中断服务,因为它不需要保存上下文。

不好的一面是, Forth 可能有些慢。在一个大的程序中,它可能比最新的 C 语言产生的代码占用更多的空间。然而,尽管用 Forth 编写的 "Hello world" 程序可以达到 2K 字节,但是它不需要装载更大的运行时间库。 Forth 鼓励程序员使用定点表示法,这可以极大地提高运行速度,但是在编码时需要更多地进行分析。

Forth 的最大缺点如同"第 22 条军规"。知道 Forth 的人不多,而人们又通常不愿意学习某些东西,除非其它的人都希望使用它。这就是盖茨先生的生活方式。

如果你能够说服你的老板让你使用 Forth ,它将成为你的秘密武器。工业经验显示 Forth 程序员可以达到C程序员 10 倍以上的生产率。

我们这里给出一个 Forth 和C语言差异的例子。这是一个嵌入式程序,使用板上的 PIC 驱动晶振。我们用 Forth 编写了一个程序以显示 PIC 程序员如何工作。下面的列表1是这个程序外层循环的 PDL 描述。列表 2 提供了可执行的 Forth 程序。这花了我 10 分钟时间(在一个实际的 Forth 程序中,这些代码将要被因子化成几个定义),而列表 3 是同样程序的 C 语言版本。

列表 1 一个抖动产生器的顶层程序 PDL 描述

Main Program:

HouseKeep (set ports, clear flags, set defaults)

Read upload bit (has user saved previous settings?)

If low

CopyPROM (load defaults from EEPROM)

Endif

ReadConfiguration (get former settings from EEPROM)

SetConfiguration (set board registers)

Beginloop: (Start of endless loop)

Read self-test bit

Read self-test number

If bit=0 and number <>0 (self test operation)

Case: (test number)

On 1 do test 1

On 2 do test 2

On 3 do test 3

On 4 do test 4

Endcase;

Else (normal operation)

Read interface flag (Check for faults or user input)

If set

Read status word (Identify faults or user input)

If fault flag, do soft reset, endif

If jitter flag <> jitter state, toggle state, endif

If calibration request, Calibrate, endif

If Bit 0, SetAmplitude, Endif

If Bit 1, SetBitRate, SetAmplitude, Endif

If Bit 2, SetBitRate, SetAmplitude, Endif

If Bit 3, SetFrequency, Endif

If parameters have changed

Update EEPROM

Endif

Clear interface flag

Endif

Endif

Endloop;

进一步学习

要学习更多 Forth 知识,最好的办法是加入非赢利的 Forth Interest Group (FIG) 组织。它们出版一本名为《Forth DIMENSIONS》的杂志,也销售图书和公共域版本的 Forth 系统软件。

经典但是有些过时的书是 Leo Brodie 的《 Starting Forth 》(中译本: 《Forth 语言入门》)。如果找不到它,可以从FIG购买。 Brodie 的《 Forth 思维方式》没有告诉你如何使用 Forth ,但对 Forth 和其它语言的结构和哲学思想进行了很好的考察。另一个好的入门书是 C. Kevin McCabe 的《 Forth 基础》第一卷。

为了用 Forth 实现一个嵌入式系统,你可以先得到一个公共域版本,你也可以根据你的处理器购买一个现成的版本。 Forth Inc. 提供基于 DOS 的版本,可用于80196、80186、68HC16 和 TMS320C31 ,它们也有用于68HC11和8051的 Windows 版本。

一些小的 Forth 开发商在《 Forth DIMENSIONS》 上作广告。

为什么不使用 Forth

人们常说 C 语言程序员很容易找到而 Forth 程序员却很难找。这是事实。很少的“程序员”知道 Forth ,但是,我们发现硬件工程师却常常熟悉它。有经验的工程师通常比职业程序员能写出更好的嵌入式代码,因为后者不熟悉硬件。

你需要知道你公司的目标是什么。如果你真的需要产品,那么你就需要 Forth 。这就是路。

------------------------------------------------

Tom Napier 曾是火箭科学家,健康学家和工程管理,最近九年时间从事宇宙飞船的通信设备开发,现在是顾问和作家。

你可以通过电子邮件与 Eric 联系 http://www.voicenet.com/~eric/Forth.htm

 

Forth 研究 相关文章

1x Forth

Forth 仍然适用于嵌入式应用

 

 

 
   

(C) ForthChina.com 版权所有 2004-2010
Email:forthchina@163.com