分享
 
 
 

组合语言之艺术4

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

第三章 基本认识

第一节 应用工具

一、对程式的认识

写作程式不难,但要写出好程式却不容易。这就好像画图一样,人人都能画,而画出来的图却可能有天壤之别。

想作一个好画家,首先要有观察及分析的能力,面对着杂乱的事物,先整理出头绪,找到主题。再在画布上勾出轮廓,这叫做「布局」。布局完毕,根据实际的环境,决定作图的先后「顺序」。顺序是一种层次观念,景物及色彩都有一定的层次,绝不可随意所之,想到哪里,画到哪里。

观察考虑完毕,即开始准备,先将画笔、调色板等工具放妥,把要表现的主要色彩也调好。最后是选择适当的画笔,蘸上色彩,按照所观察的结果,涂在画布上。

画图颇重风格,有些个人主义的艺术家,技巧并不精通,只因为时代潮流或历史条件,创造了某种独特的风格,就得以成名享利。一般的画家则不然,不论是「工笔」抑或「写意」画,全靠其技巧及素养,始能求生存。至于艺术大师,则首重风格,再加上素养、技巧,方可扬名立万,永垂不朽。

最糟糕的画匠,既没有观察能力,更谈不上技巧和风格,除了照着别人的作品抄袭、模仿外,创造不出有价值的作品。若程式师也如此,只能照着别人的意思,填填指令,不过是个程式匠罢了。

在观察分析之下,把欲表现的内容整理成为具体的步骤,用电脑术语来说,是为「程式分析」,相当于画画中的「布局」。再下去,便是「流程」制作,或是作画的顺序。将各种程式的层次安排妥当,才能开始写作程式,相当于开始作画。

这些观念牵涉甚广,不是三言两语可以说完。本书仅以组合语言写作的训练为目的。如果读者能善用组合语言的各种技巧,又能充份认识所要完成的工作,至少可以满足「工笔画」的条件。对一个电脑程式而言,目前画「工笔画」的价值要比「写意」为高。

下面,我们要以工笔画的立场,来理解组合语言的应用。对油画或水彩画而言,色料相当于程式用的「资料」,调色盘就是运用资料的「暂存器」,画笔等于「指令」,一切都准备妥当,所谓「作画」就是「写程式」。

程式是由一系列的定义和指令组织成的可执行的程序,需由一种档案的形式(.ASM)经过编译程式 (MASM.EXE) 的处理,将原始档转变为目的档(.OBJ),然后再将一个或数个目的档经过联结(LINK.EXE)成为执行档(.EXE),或者再用 EXE2BIN. EXE 制成记忆限在64KB以下的命令档(.COM)。

程式师应熟悉上述过程中的每一细节,方能顺利完成程式写作。

程式的写作方式本无定则,完全看需求及应用而定。可是正如一幅画,在布局时,程式师应该先有全部的观念,然后逐步实行。为了提高效率,这些步骤,有必要加以归类。结果就是所谓的模组。

模组的良窳,决定了程式写作、修改及再应用的效能。在写作时要求理念一贯,连续进行。修改应方便灵活,不致错误丛生。而应用上功能要完整,可以独立调用。

根据上述条件,程式的结构大致上可分为:

1,主程式:连贯性的处理过程,应该一次考虑清楚,细节暂 时放在一边,先把大架构写出来,以免顾首不顾尾。在空间足够的情形下,大架构应该是一个完整的模组,且在整体的观念下,统一处理。

这种做法,对程式侦错及修改有很大的帮助。因为修改和调整最多、对功能影响最大的,必然是主程式。若主程式都在同一模组中,比较容易得到理想的效果。

2,副程式:副程式都是一些细节的处理,可以用‘CALL’的方式执行。原则上说来,细节的处理经常重覆发生在不同 的情况下,作为副程式相当有利。只是应该注意调用的手 续,为了效率,通常将需要处理的参数或资料,经由暂存器或者必要时用缓冲器载入。

既然是数个程式均可共用的副程式,而且此类程式为一独立的过程,所以应该事先分别测试,保证无误。

此外,各副程式的入口处,宜明白的交待暂存器的使用方式,且要能一目了然。

3,子程式:子程式与副程式有一点不同,就是具备完整的机能。所谓完整的机能,指该段程式可以独立执行、有固定的功能。在应用时,两者没有分别,然而在写作时,子程式的考虑要慎重些。

4,资料档:资料档也可以视为一种静态的程式,虽然不是执行用的,但却是执行时不可或缺的素材。资料档的设计应该注意空间的利用,等长度的资料结构最具效率,最好保证资料起点为双数,以节省16位元汇流排的执行速度。

在应用缓冲器时,切忌随意设置,往往程式师们设了一缓冲器,等后来发现没有必要,再想删掉就麻烦大了。所以事先应安排妥当,以便于随时查找和调整。安排的方式视使用的情形而定,有的以模组归类;也可以用字母排序为依据;再不然就加上详细注释说明功能及使用的程式标号。

5,应用表:在本书第四章第六节将介绍应用表的功能和应用方式,此类表一次设计完成以后,很少需要再修改,为了工作效率,独立成为一个档案,自有其必要性。

此外,各种程式的命名最好能有代表性,以便于应用;程式不能太大,否则编辑耗时费事;分档时,则要注意标号宣告及各段安排的问题。在磁碟中,应该专辟一个子目录,不要把各种不相干的程式,都混合在一起。

第二章三、四节中已规定了格式的标准,此处仅再补充一点。即各缓冲器的定义与使用时的长度应相等,否则在编译或联结时,容易发生错误。联结时,有时并无足够的错误讯息,供程式师得知错误产生的原委。最难理解的错误,往往与缓冲器的定义有关,即定义的类型与使用的类型不一致。另外一个情况是段值的改变,其补救方法为在应用时,临时加一「前置定义」。

所谓「前置定义」是指当暂存器为一字元时,其前应加写BYTE PTR,否则用 WORD PTR 以确定其值,即可保证安全。

如:MOV BYTE PTR BBSDOT1,AL

此外,每当段值有所改变时,都加写一条:

ASSUME CS:XXX,DS:YYY,ES:ZZZ

这种用法,完全是给汇编程式「看」的,程式本身并没有增加任何指令。

其他规定,请参阅各相关手册。

二、对资料的认识

在画布上,所有色彩都是由红、蓝、黄三原色及白色调制而成,了解色彩的变化是画家的基本素养。在电脑中,所有的资料则都由二进位数据组成,要写程式,必须对二进位的特性先有深刻的认识。

绝大部份的程式师,都不知道二进位数据的妙用,充其量能够很快地换算二进位与十进位的数值。再不然,由二进位值领会到图形的点阵排列,如此而已。

二进位就是开关的观念,把一连串的开关联在一起,其所能发生的作用,完全在于每一个开关、以及各开关组合应用的功能。

说得明确一点,先要将各种需要设计的功能分析清楚,找出其共通的因素,如果这些因素能用「开」及「关」两个简单的状态代表,则可以用二进位制加以控制。在理论上,一开一关只有两种作用,而两组开关就有222 种作用,最理想的设计 是将开关的排列组合数用到极限。

举例而言,电脑上应用的彩色,就是最理想的设计之一。在电脑中,最基本的应用单位为「字元」(Byte),每一字元有8个「位元」(Bit),相当于8个「开关」。为了要最精简地应用多种彩色,只以三原色与辉度组合,八个开关就能产生 256种不同的彩色。兹将各开关所代表的彩色分列如下:

开关一 (bit 1):正蓝色

开关二 (bit 2):正绿色

开关三 (bit 3):正红色

开关四 (bit 4):灰色 (高辉度)

开关五 (bit 5):黑色 (低辉度)

开关六 (bit 6):浅蓝色

开关七 (bit 7):浅红色

开关八 (bit 8):浅青色

★上述 (bit n) 是从 n=1 开始计算。

应该注意的一点,是电脑的基本单位在于八个开关,不用足就是浪费。如果8个不够,再增加便有16个。所以,因事制宜,在设计的时候,唯有用8的倍数才划算。

但是,宇宙中的事物,不见得刚好是八的倍数。如果设计的人没有这种认识,不能把所处理的资料,以8为限制条件去划分,就无法利用这种有利的条件,当然,也就得不到最理想的结果。

所以,要想程式具有最高的效率,首先要把资料整理成为八的倍数值结构。把资料整理为最有效的结构方式,称为「资料结构」,关于这一点,在后面将有较详细的例证。

每个字元有 256种排列组合,即相当于 256个十进位的数字。为了方便人的理解,通常将字元写成十六进位形式,并在其数字后加一‘H’,以别于十进位数字。

兹将十进制与十六进制对应表列于下面:

二进位值 八进位值 十进位值 十六进位值

0 0 0 0H

1 1 1 1H

*10 2 2 2H

11 3 3 3H

*100 4 4 4H

101 5 5 5H

110 6 6 6H

111 7 7 7H

*1000 *10 8 8H

1001 11 9 9H

1010 12 *10 0AH

1011 13 11 0BH

1100 14 12 0CH

1101 15 13 0DH

1110 16 14 0EH

1111 17 15 0FH

*10000 *20 16 *10H

★ 凡前有 *者表示进位。

★★二进位数后应加‘B’,八进位后应加‘O’。

由上可知,十六进制仍沿用十进位数字,只是到了10时,已无现成数字可用,只好借用英文字母。在程式中,汇编程式为了分辨ASCII 字符与十六进制数值,通常规定凡十六进位数值以英文字母开始者,在其字母前加一‘0’。

三、对暂存器的认识

暂存器 (Register) 相当于调色皿,资料相当于色料。把色料放进调色皿里,为的是要得到预定的效果,暂存器对于资料亦然。

调色皿有大有小,深度有深有浅,其目的是针对不同的情况,以作有效的处理。暂存器也是一样,应用得好,程式会很精简,容易修改、阅读。否则,想到哪一个就用哪一个,没有原则,没有章法,这种程式委实不敢恭维。

暂存器的重要性,在于处理方便灵活、速度快,占用空间小。不幸8088 CPU的暂存器很少,用起来总是捉襟见肘,辛苦异常。正因为此,暂存器的善用与否,成为程式效能高低的关键技术。

有些程式师不愿意精打细算,经常设定一些「缓冲器」,利用缓冲器可以任意定名、便于记忆的优点,竟把珍贵的暂存器,当作各缓冲器间、搬运资料的交通工具,只见资料不停的搬进搬出。虽然程式师省了点事,但运行速度白白浪费了,空间也被糟蹋了。写这样的组合程式,远不如去用高阶语言。

当然,缓冲器是有必要的,但也只限于「必要」的情况,而且,在程式规划时,就要考虑各种应用的条件,把缓冲器内的值取出后,一次处理完毕。如果不能一次解决或是经常要用到的资料,则设法放在暂存器中。

实际上,任何程式不可能在一个过程中,同时需要很多特殊的资料。好的程式师能把复杂的工作处理得有条不紊,功力不够的,往往把简单的事情弄得令人难以理解。8088的暂存器的确是不够用,但是却不至于少到要以缓冲器取代的地步。

工作的好坏、成败,与人的组织能力有绝对的关系,限于篇幅,我们不能多谈。可是,利用暂存器的特性,来处理繁杂的资料,倒也是训练组织力的方法之一。

首先,我们应该把暂存器视为工具,了解工具的功能、性质,然后要能铭记于心,纯熟地加以运用。

根据个人的理解,暂存器概分六类:

1,分段用

程式段 CODE SEGMENT :CS

资料段 DATA SEGMENT :DS

堆栈段 STACK SEGMENT :SS

特设段 EXTRA SEGMENT :ES

2,堆栈用:

堆栈值 STACK POINTER :SP

栈用器 BASE POINTER :BP

3,记忆转换用:

源存器 SOURCE INDEX :SI

终存器 DESTINATION INDEX :DI

4,一般用:

累积器 ACCUMULATOR :AX

兼用器 BASE :BX

计数器 COUNTER :CX

资料器 DATA :DX

5,标志用:旗号值 STATUS :FLAG

6,指示用:执行值 INSTRUCTION POINTER :IP

为了便于记忆,我们给暂存器定中文名,其定义为:

凡分段用者率称「段」,做为各段起始位置指示用,其计值方式为:系统中的绝对地址=(本值×16)+各段定址值

如:资料段为 1600H,乘16即为16000H。

如源存器为 1234H,则此源存器在系统中由0算起的地址为:17234H。

应注意者,各种以「器」定名的暂存器,皆有限用的段,切勿混用。

凡定名为「值」者,皆为不能用来供程式写作的暂存器。如堆栈值(SP)系指示堆栈所在位置;旗号值(FLAG)表示旗号标志的情况;执行值(IP)则代表程式当前所执行的地址。这些暂存器值并非不能改变,但对技巧尚不够纯熟者,最好保持原值,不要妄动。

经常使用的「器」有两种,一以16位元为单位,如栈用器、源存器及终存器; 另一种则具有两个分别称「高位」及「低位」、各有8位元,可单独使用,也可合并为16位元的暂存器AX,BX,CX,DX。

暂存器通常作为容器用,但有些多用为记忆区之定址,以便将其中贮存的资料取出应用。前者称为容器功能,可以作计算、逻辑处理等。后者称为定址功能,系供处理各「器」所定位址的资料用。由于8088 CPU的定址方式,受限于当初不成熟的设计理念,偏偏 IBM独具慧眼,选中了它,所谓城门失火,殃及池鱼,读者不得不多花点功夫,小心应付。

栈用器(BP)属于堆栈段的记忆位置,系提供给高阶语言结构使用,对组合语言来说,功能不大,但若善于运用,也不无价值。

源存器(SI)固定指向资料段,将源存器中的资料取出,所指的是取出资料段中的资料。设若

DS=2000H SI=1234H,则

SI中的1234H 系指系统中 2000H×16加上位址值 1234H。

不过,使用者不必去计算,只要知道是由资料段起,位址为1234H 即可。

终存器(DI)较为复杂,通常它是指向资料段,可是有几个指令涉及大量资料转移,需要由源存器搬到终存器。由于受限于分段的设计,为了便于段间应用,所以特别规定:在这种情况下终存器系指向特设段(ES)。也就是说,只能由资料段移向特设段。程式师可以先设定各段的段暂存器,再作转移。若要在同一段中作资料转移,则应使资料段=特设段。

一般用的暂存器,都可以分成两个8位元、各命名为高、低位暂存器,如:

累积器:AX 高位 AH ,低位 AL

兼用器:BX 高位 BH ,低位 BL

计数器:CX 高位 CH ,低位 CL

资料器:DX 高位 DH ,低位 DL

其中累积器的功能最强,可以做乘、除计算,AH尚有贮存旗号的特殊指令。尤其是从记忆区中取值或将值放进记忆区内时,效率最高,如 LODS , STOSW等。

由于其功能高,运用灵活,所以宜于打杂,千万不要赋与固定的使命。

兼用器则有一种重要的特性,它是一般用暂存器中,唯一能自记忆区中读取资料者(XLAT指令除外),所以作为「资料及定址转换」 (后文将专门介绍此一功能) 方便异常。

计数器常用作「回路」或次数的记录,也有专用的指令,除非不得已,或者计数用得不多,最好保留备用。

资料器功能最少,最好固定其用途,选择经常需要应用的资料,置放其中,以便发挥时间空间的最高效率。

四、对指令的认识

指令就是「指挥」、「命令」,用以控制电脑,一步一步地实现程式的计划。

组合语言的格式为:

( 下行中凡标“[ ] ”者,表有些指令可省略 )

[前置元] 指令 [目的操作元,源始操作元]

1,「前置元」:以下诸例即为前置元的用法。

11段名:表后面的操作元应属于此临时前置段。如:

MOV AX,CS:BUF1

12定义:表示其后缓冲器的临时定义。BYTE PTR表示以一个字元定义的资料; WORD PTR表双字元资料。

不论缓冲器的原定义为何,凡有前置元者,皆以临 时定义为准,如:

ADD BYTE PTR BUF1,CL

前置元除了定义缓冲器长度外,亦可表示距离,

JMP SHORT ABCD

2,指令:

11使用方法:

1-1 暂存器到暂存器,但限长度相同者。

MOV AH,BL ; 为字元

XCHG AX,BX ; 为二字元

1-2 暂存器到缓冲器,或缓冲器到暂存器。

OR BUF1,AX ; BUF1为缓冲器,WORD

ADD CL,BYTE PTR BUF1

1-3 数值与暂存器或缓冲器之间。

TEST DI,8000H

AND SI,0FFH

SUB BYTE PTR BUF1,3

★数值绝不可作为「目的」操作元

1-4 将记忆区的地址放在暂存器中,以传送该地址的内容,或传送变数以便间接调用资料。本法限用于源存器(SI)、终存器(DI)、栈用器(BP)及兼用器(BX)。如:

MOV AL,BYTE PTR [DI]

XOR [BP],DL

MOV AX,[DI][SI]

MOV AX,BUF1[DI]

JMP LAB1[BX]

1-5 执行指令本身,不需源始或目的操作元。

PUSH CS

POP DS

CALL ABCD

JMP ABCD

CLI

STD

LAHF

RET

1-6 执行计数者。

LOOP ABCD

REP MOVSB

SAL DL,CL

ROR AX,1

DEC BX

1-7 暂存器专用指令。

OUT DX,AL

MUL BUF1

DIV CX

STOSB

LODSW

1-8 条件执行者。

JNZ ABCD

JA ABCD

JCXZ ABCD

INT 10H

IRET

12应用功能可分为下列八项:

2-1 资料转移:1-1,1-2,1-3,1-4皆有可能。

2-2 旗号控制:1-5 涉及旗号者。

2-3 段址处理:1-1,1-2 项可能。

2-4 数学计算:视指令而定,上述各项皆可。

2-5 字串处理:1-6,1-7 项功能。

2-6 控制转换:1-5。

2-7 条件执行:1-8。

2-8 中断处理:1-8。

3,操作元:可分成暂存器、缓冲器及数值(Immediate Data)。其书写方式与习惯的由前到后正好相反,使用时要小心,其余细节请参看有关组合语言手册。

第二节 工作环境

一、系统空间

IBM PC的记忆区定址,是采用倒装方式 (Big Endian) ,即定址值系由大到小,不同于一般由小而大(Little Endian) 的定址常识。

不论当初如此设计的目的何在,这种与人的习惯相反的观念,给写作组合语言者带来极大的困扰。不仅初学者常莫明其妙,连我个人多年来一直与图形处理为伍,都感到汗颜。每次在处理图形时,一定要将原图画在纸上,对照参详,才能了解是怎么回事。

举例说,有个图形值在AX中,要写进 DI 所指记忆区位置中,写完以后,AX要向右移一位再继续写,直到CX=0。

这是一个非常简单,而且经常用到的动作,可是在使用「倒装定址」时,麻烦就来了。

假设AX值为4567H ,DI指向记忆区2000H ,倒装的放法,是先将AL的值放进2000H 的记忆单位中,再将AH放进2001H 的记忆单位里。如果从由小到大的定址观点来看,这就等于是在2000H 中放了一个十六位元的值6745H 。

这倒不打紧,因为再从记忆位址2000H 中放回 AX 时,仍然成为4567H 。问题是在作图时,一旦4567H 变成了6745H ,图形就左右颠倒了。补救的方法,是在放进记忆区之前,先将AH及AL交换,放完以后,再重新交换回来。说来不算大事,可是白白浪费了两个指令的时间及空间。对速度极关紧要的画图显示而言,要画几万个点,所累积的时间就不可小观了。

除此之外,在写程式时,对图形的效应要能掌握,才会有良好的成果,像这样每次转来转去,头都昏了,自然而然就失去了耐性。

现在,80386 CPU 问世了,且不谈效果,读者可以试想,把32位元的 12345678H转换成 78563412H要多少道手续?

这种痛苦的手续,也是美国人不愿意用组合语言的理由之一。在高阶语言中,有编译器代劳,问题好像不大。但对效率的要求而言,就得不偿失了。图形功能是当今及未来电脑的主流之一,由于当初设计者没有远见,导致无穷的后患。

问题尚不止于此,IBM PC/AT 的系统空间,在定址的理论上,可以有 1MB(暂时不必考虑记忆扩充及EMS 等问题),然而真正能提供作为程式执行的空间,却不足 600KB。

我们且看其系统空间的安排:

0000H 段 0000H-007FH 计 128字元,为32个基本中断。

0008H 段 0000H-0380H 计 896字元,供系统管理中断。

0040H 段 0000H-00FFH 计 256字元,为基本程式资料。

0054H 段 0000H-9C00H 约 34K字元,DOS 程式占用。

唯有在 00E1H段-09000H段的前半是使用者可以控制的空间,其后,又被系统占用:

09000H段由0A000H附近直到0FFFFH,为DOS 所用。

0A000H段,为 VGA图形显示区。

0A800H段,为 EGA图形显示区。

0B000H段,为文字态缓冲区,萤幕处理器6845自动管理。

0B800H段,为图形态显示区,萤幕处理器6845自动管理。

0C000H段,至0D000H段,各机种不定,供 EMS扩展记忆。

0C800H段,为唯读记忆体,其内为硬碟控制程式。

0E000H段,1MB 的主机此处为 RAM,否则此段不能使用。

0F000H段,为唯读记忆区,其内为基本输出/入程式。

由上可知,整个系统的规划不尽理想,尤其受限于8088的CPU 原先错误的设计理念(段暂存器现为定址的16倍,即每进一,相当于地址增加16。在最初,如果不考虑与8080兼容,原可轻易地定为 256或更高倍。)所以,当要扩充记忆容量时,便产生了 EMS这种无可奈何的高科技畸形儿。

二、周边设备

所谓周边设备,率指须透过系统的输出/入汇流埠(I/O Port),及其管理程式所控制的外部各种设置。

在此定义下,键盘就是一种周边设备,除此之外,萤幕显示器、印表机、磁碟机等,均属周边设备。显然,程式师必须了解每一种周边设备的性质,否则无法下手。

由于周边设备种类繁多,且各有其使用规格,可以说毫无技巧可言,故本书不拟一一介绍。要之,把各种设备所定义的规格条件,抄录在记事簿中,以便随时查阅。

此外,为求程式能有效地应用于各种不同规格的周边设备上,千万不可在应用程式中统一处理,最好定妥各种介面,作为附属程式,由使用者自行设定。

这样规划的第一个原因,是无人能预知到底未来需要多少种不同的设备,挂一漏万,以后程式增改不易,可能导致功能不足,或程式松散的后果。

第二个原因在,使用者经常使用的设备是固定不变的,将一些永远用不到的程式放在一起,是无谓地浪费空间。

第三个原因为技术虽在进步,程式应用观念则难以改变,主导程式与周边设备之介面程式不应纠结在一起。一个没有渣滓、精心雕琢的程式才有永恒的价值。终有一天,当电脑技术成熟时,原应用程式无需改动,仅将处理周边设备的附属程式换成新的即可。

这就是生命,就是新陈代谢,有了这些认识,才能理解组合语言的精义。

三、系统程式

在 IBM PC/AT系统中,只有两种系统程式,一是磁碟作业系统程式 (MS-DOS 或 PC-DOS ),负责系统启动、记忆区管理以及部份输出/入处理等工作。此系统程式原贮存在系统磁碟中,开机时才调入系统中,所以容易修改。由最初推出的版本1.0 ,到现在已是4.01,其功能还在不断地改进中。

另一种为基本中断服务程式(BIOS),贮存在唯读记忆体中,除非机种易动,否则永远不会改变。基本中断程式的主要功能为便利程式师,把所有的周边设备所需要的参数,统一由暂存器代为传输。程式师可按照规定,把正确的值,放到规定的暂存器中,基本中断便会优先执行。

这两种系统程式,程式师必须熟悉,至少,应知道何种功能要用哪一个中断。

这两种系统程式,都因瞻前顾后,速度不够理想。因之有些程式师,根本不用这些中断,自行控制输出/入埠。这种做法确实能提高速度,自由控制。而相对的,程式的通用性也减低了。是否值得,设计前应先考虑清楚。

此外,这两种中断程式有些相互重复之处,如键盘输入及萤幕输出等,经常令人不知如何选用。有人建议用磁碟作业的中断,我则认为该用基本中断。

因为系统容许程式改变基本中断的入口值,所有利用基本中断的程式,都可修改入口,以增加其应用功能。磁碟作业系统则不然,虽然该程式在磁碟上,且在不断地改进中,但在改进之时,又必须兼顾过去的客户。时间一久,问题就发生了。且改进越大,越显得过去的作业方式落伍,兼容就是保留过去渣滓的代名词。兼容性越高,包袱就越重,空间浪费越大。

建筑在这种基础上的程式,必须冒种风险:是否有一天,磁碟作业系统会面临运转困难或遭解体的厄运?O/S2的问世已经表明了,此系统的大限业已到来。

基本中断可以改变,意思是说,除了一部份BIOS空间的浪费无可避免外,在PC系列中,系统中断的观念不会再改变。只要程式师能把握基本中断程式的技巧,则不论未来的系统变化到任何地步,一个具有实用价值的程式,理论上其生命期应该是很长的。

四、配备程式

配备程式指的是一些非必要的基本程式,只因为特殊需要而调用。通常,它是由某些系统提供,配备给某些程式的。

配备程式包括各种计算的函数及绘图公式,特殊处理用的LIB.等,在某些情况下,也可以将之视为环境,例如视窗管理MS-DOS WINDOW,记忆扩充装置 EMS等。

配备程式的产生,证明了电脑软体发展的迂回历程,同时也表示出软体的灵活性。在我个人的观念中,配备程式如果能有一定的设计方式,有统一的规格,很可能在大量的、不断发展下,成为一个个「公用模组」,并可专门提供模组,以供用户应用,使得软件的制作变得轻而易举。

写作或应用这些程式,别无其他法门,唯有熟记于胸,才能得心应手。

五、公用模组

模组应是未来电脑软件发展的主流,每一类模组的功能,代表了各行各业的经验及诀窍。使用者无需了解模组的制作技巧,只要知道如何调用,就可以完成工作。

目前尚无厂商提供「公用模组」,但是随着观念的拓广,一旦有了理论,有人先行一步,这种潮流即将形成。我们即将推出的“聚珍整合模组”,第一阶段尚限于程式师使用,再下一步,当客户直接调用的介面完成后,程式的发展方向又将改弦易辙了。

第三节 处理对象

一、数据资料

数据资料率指可以输入、处理及计算的二进位资料,在工作过程中,安全性为第一考虑因素,同时要兼顾精确以及完整性。此类资料一般说来数量都相当大,要妥善规划资料长度,否则存贮空间会成为执行程式时的主要课题。

写作此类程式时,各种进位制的转换,显示区的定位,计算公式的处理等都应该作为子程式,以便任意调用。

而真正关键问题却在于:数据的极限是否能够明确得知,在有限的范围中,绝对可以设计一种「结构化」的规格,符合效率的需求。否则也应根据其规则性,配合程式的特性,有效地加以处理。

二、文字资料

文字资料多为字符态,拼音文字所应该注意的是,字与字间的空间调整,齐头、齐尾、齐中等变化,行末断字的规定,以及字体、字形、字号等。

中文尚有输入码、内码等处理问题。原则上,如果要考虑中、英文兼容,则应注意萤幕上的字形显示与字码记忆区的位置,应占相同的比例。

目前,由于英文字、码不分,皆占一字元,萤幕上标准格式为25行80字,即采用所谓「文字状态」。而中文字形至少要有16x 16点阵,且需用图形方式(也有采用文字态,再加特殊硬体者,但成本偏高,有碍中文电脑未来发展)。因此,当采用640x 400或近似规格时,中文字形与英文之比,约为2:1。

在此基础上,以二字元为中文的内码长度,是最常用的结构。但是随着技术及观念的进步,有些英文系统已在使用二字元码,是则,中文有使用四字元的必要。

从另一方面来看,大陆所用的「国标码」,系抄自日本五十年代的 JIS CODE -日本工业标准,最多仅能容纳8836个符号,其中「汉字」尚不足八千。而国标码更为精简,收字6763个。中文源自中国,现在却借镜东洋的「工业标准」,且摇身一变,竟成为十余亿人口的「国家标准」,真可谓每下愈况。无独有偶,台湾也有所谓的标准,BIG-5 的13,053字,虽然是国标码的两倍,二者终究是五十与百步之差而已。

为什么要订定这种「辱及先祖」的文字标准呢?谁又够资格订定中文标准呢?从事电脑工作者不过是些「技术专家」,连电脑这一行所有的技术尚未必精通,更何况隔行如隔山,竟然捞过界,捞到文字界这个相离十万八千里的领域去了。

文字是人类思想、文化的载具,先贤先圣们殚精竭虑所创造的文字,就是用来传达他们对宇宙、人生的认知。我们后代子孙不肖,不能领悟其微言大义倒也罢了。对电脑技术了解不足,没有能力令电脑应用中文,这也可以理解。但自以为是,依权仗势,妄想偷天换日,仅用少数认识的文字,定为整个国家的「文字标准」,并强制国人接受,这种颟顸就难以令人苟同了。

在运用中文时,由于各家发展的系统观念不一,有的甚至违法盗袭国外软件,为了兼容起见,必须「削足适履」。原则上,中文内码将第一字元中第八位位元设为一,得以与「美国工业标准码」的ASCII (American Standard Code for Infor-mation Interchange)有别。

文字资料处理上最重要的工作,是排序的技术问题,国标码仅六千多字,却分为二集,把常用字放在前集,次常用字在后部。但是这种顺序与使用人的观念毫无关连。除了统计这种使用频率的学者专家外,不可能有几个人理解何字是常用字,何字不是。

于是,当我们要利用电脑的高速效率,将输入的中文加以排序整理时,国标码完全起不了作用。也就是说,编码原为提高效率,而我们的编码只为了编码,与效率毫不相干。

唯一的补救办法是,再建一个排序表,与国标码一一对照使用。

高科技界因为利润高、地位高,故而高论、高见特多,只是动起手来就难免「眼高手低」,再不然则是「高论调、低效率」。

相信人人都有查字典或电话簿的经验,对用英文来说,是轻而易举,中文则麻烦多多。国人只知抱残守缺,自卑自怜,而不求了解其因果原理。一般人如此倒也罢了,高科技界倘如此,就有点说不通,甚至令人怀疑是否别具用心。

英文所以方便无他,因其字母具有直接索引的功能!中文则有前人订定了一套「部首、笔画」的索引观念。这在过去资讯不发达的时代,的确是个创见,也足敷应用,但是现代与字母的直接索引相较,在效率上究竟差了一大截。

也有人认为,我们要维护中华文化,就应该死抱着古人所定的索引观念。这种说法只有一点不足,就是忘了把大汉衣冠也穿得整整齐齐,甚至用文房四宝取代现代化事务工具!

麻烦的是,「部首、笔画」是两种不同的索引观念,当没有时间因素介入时,孰先孰后关系不大。可是用到电脑上,就必须定先后次序,否则碍难执行。

对姓氏笔画少的人,当然主张「笔画」优先,姓氏部首明显的,则主张先排「部首」。这点不难理解,出席一个重要的庆典,或在报上亮相,人数一多,排名先后所涉及的利益,至关重大,不能不争!问题在于,除了私利外,部首笔画这种没有效率的索引观念,还有什么实用的价值?如果一定要保存,作为一种特例,当然可以。可是电脑所追求的是效率,每个中文的部首和笔画,都需要建对照表,才能应用,字集越大,空间需求越大,时间消耗也越长。

这还不说,索引不仅是提供给电脑用的,人更需要。仅以查电话本为例,「张伟雄」这个名字,我们凭什么知道其前后的「定位」关系呢?表面上看来,只要查三次部首及其笔画、以及数三次这三个字的笔画。而事实上,在查找的过程中,每遇到一个名字,都要重覆前述的手续,才能加以比较。

有人振振有词说,有些字一眼看过去就知道是几画!至少我个人没有这种本事。而且根据统计,中文平均以十四画的居多,由九至十八画,就很难靠视觉分辨!再若人名一多,视觉就很容易疲劳。

又有人说话了,现在是给电脑排序,与人不相干!殊不知字母排序可以立即执行,而部首、笔画排序要多作三至六次动作,兹以先部首后笔画为例:

1,查本字之部首序值。

2,查对照字之部首序值。

3,比较两者之大小,决定是否需要再比。

4,再比时,查本字笔画数。

5,查对照字之笔画数。

6,比较两者之大小,以决定序位。

把这些步骤写成程式,以中文两个字元的内码计,(意思是说中文收字在两万以下)如果用对照表的方式,空间当在64KB以上,速度则较英文慢约50倍。再若采用公式计算,空间或能节省,但速度将慢上千、百倍之多。

这还是指两万字以下的情况,若采用汉字全字集,后果将不堪设想。所以「专家」们一致认为,为了效率,字收得越少越好!

怎样才能算是真正的「中文电脑」?我十多年前所面对的「敌人」,是主张将中文字埋葬掉。这种人不难对付,因为到底他们还是中国人,在民族大义的旗帜下,多多少少心中也存着乐见中文电脑成功的意愿。所不同的,只是他们不相信有此可能罢。

现今的「敌人」则顽强得多,他们同样喊着民族大义的口号,又是公认的中文电脑「专家」。更可怕的,目前使用中文电脑的人,不见得对中国文化有什么明确的认识,有个工具列印一些文件,就相当知足了。于是,这些客户也在其主观的立场,认定目前这种「市场占有率高」的半调子,就是「中文电脑」的标准!

是吗?如果中文字有六、七万字,而目前只能用几千、甚至一万多字,那么其他的字呢?算不算是中文,如果算,为什么「中文电脑」中没有?这种电脑能说是「中文电脑」吗?

有人又说了,没有关系,以后再说。怎么说呢?有一种方法,是将文字「分集」,分成:常用字、次常用字、次次常用字、罕用字、罕罕用字等等。且不管是哪位学者有这么大的学问去「分集」,我所知道的只是用这种方法,人无从记忆,中文排序的难度又一倍一倍地加了上去。也难怪当初有人认为中文不科学,这不是明证吗?

其实,中文排序根本没有问题,我们利用仓颉字母作为索引,效率与英文相等,而且收字可以高达千万个。至于记忆空间,一个字元都不需要。更有利的是,用作字典、电话簿等的索引,一查即得。

内码是各个系统、根据其不同的需求、所订的一种资料形式,没有任何理由强制规定。当然,如果内码种类多了,姑不论其编码的好坏,各个中文系统之间,自然会形成难以沟通的障碍。于是有必要建立一种「交换码」,供不同系统的内码,统一交换应用。

这种交换码才有标准化的必要,而且订定之时,应该谨慎从事,要能容纳所有各家系统所收的字,否则无从交换。

不论是哪种码,必然会有其特殊规定,在写作之前,程式师一定要设法找到该系统所用的「码表」,否则无法处理。

三、图形资料

在电脑图形资料的处理方面,目前只有点阵及向量两种形式,前者即二进位资料 (Digital Data) ,后者则是绘图用的公式值。实际上,还有所谓「概念资料」的形式,将视觉效应经过分析后,整理成为人能够理解的「概念」。这种概念资料非常精简,便于贮存,取出后,再通过「概念作图」的过程,还原成为图形。

一个优秀的画家,必然有这种概念作图的能力,只要把画家的经验写成程式,将其记忆的特徵设计为资料,电脑必将忠实地执行,而且每次都画得一模一样。

如果是处理二进位点阵资料,不外乎是压缩、还原、截取及综合等几种简单功能。绘图向量值则比较复杂,涉及计算、调整、变数、层次等多种技巧。

简单地说,绘图资料所考虑的,比文字资料难度高,要想得到理想的效率,最重要的应是资料结构的定义,其次是层次的安排,以及特徵性质的描述等。此外,输入变数处理涉及人的应用方式,除了专业人员外,多数人尚未能适应这种新的绘图观念,经验的不足,以致迄今尚未制作出理想的程式来。

概念绘图必将成为未来的主流,它不仅符合人类的认知习惯,且易于应用。只要概念资料建得周全、完整,略为改变其中一些概念元素,就能得到各种结果。

四、概念资料

人类系以概念进行思考,并透过概念来认识外界。所以,对人而言,最有效的应用方式,就是人已经熟知的概念。

概念并不是语言,而是组成语言的最基本因素。每一个人对外在世界的认知,都是独一无二的,由于人类生存在群体空间里,需要经常彼此交换经验,于是利用听觉效应表达概念,便产生了语言;利用视觉符号,则产生了文字。

前述的图象概念资料属于「具象」资料,除了具象以外,还有抽象的,包括主观的感受、认知、欲望等等,因与主题无关,这里不加讨论。总之,这些概念资料的结构,在电脑中必然是二进制的形式,只是因每一个设计者观点的不同,性能有所区别罢了。

直到如今,尚未见到实际应用概念资料的程式,但是它将成为电脑的基本结构,却是指日可待的。

作为程式师,天天与电脑为伍,不能不知道电脑未来的趋势,更不能不多加努力,掌握技术发展的机先。正因为概念资料尚未定形,人人都有相同的机会,做一个开创时代的先河。否则,等到大局底定时,只有在后面苦苦追赶,由不得己了。

五、综合资料

功能较强的程式,很少仅具有单一的资料。尤其是「整合软件」越来越受到重视,各种资料最终都将综合在一起。

综合资料有两种意义,一是人所认识的输入资料,一是电脑贮存的处理资料。

输入资料又可分指令及字符两种,在传统的观念里,不将指令视为资料,因为指令一旦执行以后,即不再发生作用。可是,在桌上型排版软件广泛流行以后,为了控制版面,必须将相关指令随资料同时贮存起来。而排版已经成为电脑重要的功能之一,所以在未来的发展上,输入资料必须考虑到指令。

在整合观念中,输入资料应有统一的规定,亦即不论是何种性质的软件,其键盘的应用、字符的定义等,都应该有全面的考虑。

关于资料内容,也有 ASCII字符及「世界字符」之争,对早期的英文系统而言,其他文字无关紧要,所以没有适当的「世界字码」可供应用。然而,资讯时代究竟不是英文使用国家的专利,在各国觉醒之际,都憬悟到字码的重要。不论 ISO国际组织如何面对问题,我个人不相信世界文字在其保留的、极为有限的「编码平面」上,能够发挥多大的效益。充其量,可供一段时间内、某些商业上的应用而已。

我认为真正的资讯标准,将是以各国文字为根本的自然语言,而目前最理想的方式,则为多字元的字码方案。拼音文字系统以二字元为宜,除了可以同时应用世界各国文字以外,并且符合当前微电脑的发展趋势。

在中文系统上,我们采用四字元的「自然码」,即将仓颉输入码压缩的方案。如此,我们可以使用上千万个中文字,有人会说没有人需要那么多字,但事实上有谁能预料呢?当初仓颉造字时,相信不会超过一千,如果他武断地订定「标准」限制后人用字,很难想像我们的民族还会有什么文化?

台湾曾有专家对我这种意见,表示是「不合乎潮流,注定要失败」,然而到底是谁不合潮流呢?四字元的微电脑已经到来了,而且被公认为今后的主流。在四字元的硬体结构上,自以一次读取四字元、其次为二字元最为有效。所以这些观念已经落伍的专家,还是去捞些钞票,把研究发展的工作,交给够资格的人去做吧!

第四节 指令应用

组合语言可以说是未经整理的、原始的电脑语言,读者们大可下一番功夫,找出其应用的规则,以发挥最高的效率。在下面,我仅就个人的经验,提供一些浅见,以供切磋研讨。

要写好程式,首先应熟记8088指令的时钟脉冲(Clock )及指令长度,一般组合语言手册中,都详列了与各指令相关的资料。「工欲善其事,必先利其器」,此之谓也。

本节所讨论的,是一般程式师容易忽略的细节,所有的例子都是从我所看过的一些程式中摘录下来的。看来没什么大了不起,可是程式的效率,受到这些小地方的影响很大。更重要的是,任何一个人,只要有「小事不做,小善不为」的习惯,我敢断言,这个人不会有什么大成就!

我最近才查到 Effective Address (EA) 的时钟值,我觉得没有必要死记。原则上,以暂存器为变数,做间接定址时为5个时钟,用直接定址则为6个;若用了两组变数,则为7至9个,三组则为11或12个。

为了便于叙述,下面以“T”表「时钟脉冲」; “B”表字元。其中

时钟脉冲T = 1 / 振汤频率

一、避免浪费速度及空间

组合语言的效率建立在指令的运用上,如果不用心体会下列指令的有效用法,组合语言的优点就难以发挥。

1, CALL ABCD

RET

这种写法,是没有用心的结果,共用了 4B,23T+20T,完全相同的功能,如:

JMP ABCD 或

JMP SHORT ABCD

却只要 2-3B,15T。

此外,上述的CALL XXXX 是调用子程式的格式,在直觉认知上,与JMP XXXX完全不同。对整体设计而言,是不可原谅的错误,侦错的时候,也很难掌握全盘的理念。

尤其是在精简程式的时候,很可能会遇到 ABCD 这个子程式完全独立,是则把这段程式直接移到 ABCD 前,不仅能节省空间,而且使程式具有连贯性,易读易用。

2, MOV AX,0

同样,这条指令要 3B,4T,如果用:

SUB AX,AX 或

XOR AX,AX

只要 2B,3T, 唯一要注意的是,后者会影响旗号,所以不要用在有旗号判断的指令前面。

在程式写作中,经常需要将暂存器或缓冲器清为0,有效的方法,是使某暂存器保持为0,以便随时应用。

因为,MOV [暂存器],[暂存器] 只要 2B,2T, 即使是清缓冲器,也比直接填0为佳。

只是,如何令暂存器保持0,则要下一番功夫了。

还有一种情况,就是在一回路中,每次都需要将 AH 清0,此时对速度要求很严,有一个指令 CBW 原为将一 个字元转换为双字元,只需 1B,2T 最有效率。可是应该注意,此时 AL 必须小于 80H,否则 AH 将成为负数。

3, ADD AX,AX

需要 2B,3T不如用:

SHL AX,1

只要2B,2T。

4, MOV AX,4

除非这时 AH 必为0,否则,应该用:

MOV AL,4

这样会少一个字元。

5, MOV AL,46H

MOV AH,0FFH

为什么不写成:

MOV AX,0FF46H

不仅省了一个字元,四个时钟,而且少打几个字母!

6, CMP CX,0

需要 4B,4T, 但若用:

OR CX,CX

完全相同的功能,但只要 2B,3T。再若用:

JCXZ XXXX

则一条指令可以替代两条,时空都省。不幸这条指令限用于CX ,对其他暂器无效。

7, SUB BX,1

这更不能原谅,4B,4T无端浪费。

DEC BX

现成的指令,1B,2T为何不用?

如果是

SUB BL,1

也应该考虑此时 BH 的情况,若可以用

DEC BX

取代,且不影响后果,亦不妨用之。

8, MOV AX,[SI]

INC SI

INC SI

这该挨骂了,一定是没有记熟指令,全部共4B,21T。

LODSW

正是为这个目的设计,却只要 1B,16T。

9, MOV CX,8

MUL CX

写这段程式之时应先养成习惯,每遇到乘、除法,就该打一下算盘。因为它们太浪费时间。8位元的要七十多个时钟,16位元则要一百多。所以若有可能,尽量设法用简单的指令取代。

SHL AX,1

SHL AX,1

SHL AX,1

原来要 5B,137T,现在只要 6B,6T。如果CX能够动用的话,则写成:

MOV CL,3

SHL AX,CL

这样更佳,而且CL之值越大越有利。用CL作为计数专 用暂存器,不仅节省空间,且因指令系在 CPU中执行,速 度也快。

可是究竟快了多少? 我们做了些测试,以 SHL为例,在10MHZ 频率的机器上,作了3072 ×14270次,所测得时间为:

指 令 :SHL AX,CL SHL AX,n

CL = 0 , 23 秒 n = 0 , 无效

CL = 1 , 27 秒 n = 1 , 14 秒

CL = 2 , 32 秒 n = 2 , 28 秒

CL = 3 , 36 秒 n = 3 , 42 秒

CL = 4 , 40 秒 n = 4 , 56 秒

CL = 5 , 44 秒 n = 5 , 71 秒

CL = 6 , 49 秒 n = 6 , 85 秒

CL = 7 , 54 秒 n = 7 , 99 秒

由此可知,用CL在大于2时即较分别执行有效。

此外,亦可利用回路做加减法,但要算算值不值得,且应注意是否有调整余数的需要。

10, MOV WORD PTR BUF1,0

MOV WORD PTR BUF2,0

MOV WORD PTR BUF3,0

MOV BYTE PTR BUF4,0

..

我见过太多这种程式,一见就无名火起! 在程式中,最好经常保留一个暂存器为0,以便应付这种情况。即使没有,也要设法使一暂存器为0,以节省时、空。

SUB AX,AX

MOV BUF1,AX

MOV BUF2,AX

MOV BUF3,AX

MOV BUF4,AL

14B,59T取代了 24B,76T,当然值得。只是,还是不 如事先有组织,考虑清楚各个缓冲器间的应用关系。以前面举的例来说,假定各缓冲器内数字,即为其实际位置关系,则可以写成:

MOV CX,3

如已知 CH 为0,则用:

MOV CL,3

SUB AX,AX

MOV DI,OFFSET BUF1

REP STOSW

STOSB

这段程式越长越占便宜,现在用10B,37T,一样划算。

11,子程式之连续调用:

CALL ABCD

CALL EFGH

如果 ABCD,EFGH 都是子程式,且调用的次数甚多,则上述调用的方式就有待商榷了。因为连续两次调用,不仅时间上不划算,空间也浪费。

若ABCD一定与EFGH连用,应将ABCD放在EFGH之前:

ABCD:

..

EFGH:

..

像这样,只要调用ABCD就够了,但这种情形多半是程式师的疏忽所致,如两个子程式必需独立使用,而上述连续调用的机会超过两次以上,则应该改为:

CALL ABCDEF

而ABCDEF则应为:

ABCDEF:

CALL ABCD

EFGH:

..

这样的写法速度不会变慢,而空间的节省则与调用的次数成正比。

12,常有些程式,当从缓冲器中取资料时,必须将暂存器高位置为0。如:

SUB AH,AH

MOV AL,BUFFER

这时应该将 BUFFER 先设为:

BUFFER DB ?,0

然后用:

MOV AX,WORD PTR BUFFER

如此,不但速度快了,空间也省了。

13,有时看来多了一个指令,但因为指令的特性,反而更为精简。如:

OR ES:[DI],BH

OR ES:[DI+1],BL

这样需要8B,32T,如果改用下面的指令:

XCHG BL,BH

OR ES:[DI],BX

XCHG BH,BL

则需7B,28T。

14,PUSH 及 POP 是保存暂存器原值的指令,都只需一个字元,但却很费时间。

PUSH 占 15T,POP 占12T,除非不得已,不可随便使用。有时由于子程式说明不清楚,程式师为了安全,又懒得检查,便把暂存器统统堆在堆栈上。尤其是在系统程式或子程式中,经常有到堆栈上堆、取的动作。实际上,花点功夫,把暂存器应用查清楚,就可以增进不少效率。

要知道,系统程式及某些子程式常常应用,有关速度的效率甚大,如果掉以轻心,就是不负责任!

保存原值的方法很多,其中较有效率的是放到一些不用的暂存器里。以我的经验,堆栈器用途最少,正好用作临时仓库。但最好的办法,还是把程式中暂存器的应用安排得合情合理,不要浪费,以免堆得太多。

还有一种方法,是在该子程式中,不用堆栈的手续,但另设一个入口,先将暂存器堆起,再来调用不用堆栈的子程式。这两个不同的入口,可以分别提供给希望快速处理,或需要保留暂存器原值者调用。

当然,更简单有效的方法,则是说明本段程式中某些暂存器将被破坏,而由调用者自行保存之。

二、程式要条理通顺

1,在比较判断的过程中,邻近值不必连比。

CMP AL,0

JE ABCD0

CMP AL,1

JE ABCD1

CMP AL,2

JE ABCD2

..

应为:

CMP AL,1

JNE ABCD0

ABCD1:

..

在标题为ABCD0 中,再作:

JA ABCD2

这种做法端视时间效益而定,似此 ABCD1之速度最快。

2,未经慎思的流程:

ADD AX,4

ABCD:

STOSW

ADD AX,4

ADD DI,2

LOOP ABCD

..

稍稍动点脑筋,就好得多了:

ABCD:

ADD AX,4

STOSW

INC DI

INC DI

LOOP ABCD

..

3,错误的处理方式:

MOV BX,SI

ABCD:

MOV BX,[BX]

OR BX,BX

JZ ABCD1

MOV SI,BX

JMP ABCD

ABCD1:

LODSW

..

上例应该写成:

MOV BX,SI

ABCD:

LODSW

OR AX,AX

JZ ABCD1

MOV SI,BX

JMP ABCD

ABCD1:

..

4,错误的流程:

TEST AL,20H

JNZ ABCD

CALL CDEF[BX]

JMP SHORT ABCD1

ABCD:

CALL CDEF[BX+2]

ABCD1:

..

应该写成:

TEST AL,20H

JZ ABCD

INC BX

INC BX

ABCD:

CALL CDEF[BX]

ABCD1:

..

5,下面是时间的损失:

PUSH DI

MOV CX,BX

REP STOSB

POP DI

PUSH,POP 很费时间,应为:

MOV CX,BX

REP STOSB

SUB DI,BX

同理,很多时候稍稍想一下,就可省下一些指令:

PUSH CX

REP MOVSB

POP CX

SUB DX,CX

为什么不乾脆些?

SUB DX,CX

REP MOVSB

6,有段程式,很有规律,但却极无效率:

X1:

TEST AH,1

JZ X2

MOV BUF1,BL

X2:

TEST AH,2

JZ X3

MOV BUF2,DX ; 凡双数用DX,单数用BL

X3:

TEST AH,4

JZ X4

MOV BUF3,BL

X4:

.. ; 以下各段与上述程式相似

X8:

..

这种金玉其表的程式,最没有实用价值,改的方法应由缓冲器着手,先安排成序列,由小而大如:

BUF1 DB ?

BUF2 DW ?

BUF3 DB ?

BUF4 DW ?

..

然后,程式改为:

MOV DI,OFFSET BUF1 ; 第一个缓冲器

MOV AL,BL

MOV CX,4

X1:

SHR AH,1

JZ X2

STOSB

X2:

SHR AH,1

JZ X3

MOV [DI],DX

INC DI

INC DI

X3:

LOOP X1

7,回路最怕千回百转,不畅不顺,如:

SUB AH,AH

ABCD:

CMP AL,BL

JB ABCD1

SUB AL,BL

INC AH

JMP ABCD

ABCD1:

..

以上 ABCD1这个入口是多余的,下面就好得多:

MOV AH,-1

ABCD:

INC AH

SUB AL,BL

JA ABCD

ADD AL,BL ; 还原

..

8,当处理字码时,需要字母的序数,有这样的写法:

CMP AL,60H

JA ABCD1

SUB AL,40H ; 大写字母

ABCD:

..

ABCD1:

SUB AL,60H ; 小写字母

JMP ABCD

要知道字母码的特色在于大写为 40H 至4AH,小写为60H 至6AH ,以上程式,其实只要一个指令就可以了:

AND AL,1FH

简单明了!

9,大多数的程式在程式师自己测试下很少发生错误,而一旦换一另个人执,就会发现错误百出。

其原因在于写程式者已经假定了正确的情况,当然不会以明知为错误的方式操作。可是换了一个人,没有先入为主的成见,很可能输入了「不正确」的资料,结果是问题丛生。

要知道真正的使用者,绝非设计者本人,在操作过程中,按键错误在所难免。这种错误应该在程式中事先加以检查,凡是输入资料有「正确、错误」之别者,错误性资料一定要事先加以排除。

这样做看起来似乎程式不够精简,可是正确的重要性远在精简之上。一旦发生了错误,再精简的程式也没有使 用价值。

此外,在程式中常有加、减的运算,这时也应该作正确性检查,否则会发生上述同样的问题。

三、指令应用要灵活

有一段很简单的程式,其写作的方法甚多,但是指令应用的良窳,会使得程式的效率相去天上地下,难以估计。

这段程式的用途,是要将一段资料中,英文字符大、小写相互转换。当然,转换的选择要由使用者决定,在下面程式且略去使用介面,假设已得知转换的方式。

设资料在 DS:SI中,资料长度=CX ,大写转小写时BL=0,反之,则BL=1。

我见过一种写法,简直无法原谅:

1: LOOP1:

2: CALL CHANGE

3: JC LOOP11

4: ADD AL,20H

5: JMP SHORT LOOP12

6: LOOP11:

7: SUB AL,20H

8: LOOP12:

9: MOV [SI-1],AL

10: LOOP LOOP1

11: RET

12: CHANGE:

13: LODSB

14: OR BL,BL

15: JZ CHANGS

16: CMP AL,61H

17: JB CHARET

18: CMP AL,7AH

19: JA CHARET

20: STC

21: CHARET:

22: RET

23: CHANGS:

24: CMP AL,41H

25: JB CHARET

26: CMP AL,5AH

27: JA CHARET

28: CLC

29: RET

这种程式错在把由12到29的程式写得太长,共 25B,有共用的价值,于是作为子程式调用。

试想一下,每一笔资料,都要调用一次,浪费四个字元事小,但每次要费 23+20个时钟脉冲,资料多时,不啻为天文数字。更何况这段程式写得极差,在回路中,又多浪费了几十个时钟。关于这一点,下面会继续讨论。

照上面这段程式,略加改进,写法如下:

1: CHANGE:

2: LODSB

3: OR BL,BL

4: JZ CHANGS

5: CMP AL,61H

6: JB CHARET

7: CMP AL,7AH

8: JA CHARET

9: SUB AL,20H

10: CHANG0:

11: MOV [SI-1],AL

12: CHANG1:

13: LOOP CHANGE

14: RET

15: CHANGS:

16: CMP AL,41H

17: JB CHANG1

18: CMP AL,5AH

19: JA CHANG1

20: ADD AL,20H

21: JMP CHANG1

这样的写法还是不佳,因为在回路中,用常数与暂存器比较,速度较暂存器相比为慢。应该先将需要比较的值,放在暂存器DH,DL 中,改进如次:

1: MOV AH,20H

2: MOV DX,7A61H

3: OR BL,BL

4: JZ CHANGE

5: MOV DX,5A41H

6: CHANGE:

7: LODSB

8: CMP AL,DL

9: JB CHANG1

10: CMP AL,DH

11: JA CHANG1

12: XOR AL,AH

13: MOV [SI-1],AL

14: CHANG1:

15: LOOP CHANGE

16: RET

以上这段程式,空间小,速度快,每笔资料,平均仅需不到40个时钟值,以10 MHZ计,十万笔资料,约需半秒钟!

请注意程式中所用的技巧,由2至6的分支法,就比下面这种写法为佳:

1: OR BL,BL

2: JZ CHAN1

3: MOV DX,5A41H

4: JMP SHORT CHANGE

5: CHAN1:

6: MOV DX,7A61H

7: CHANGE:

这种分支也可以由另一种技巧所取代,即预设法。事先将所需用的参数放在固定的缓冲区中,此时取用即可:

MOV DX,BWCOM ; 比较之预设值

这样程式又简单些了:

1: MOV AH,20H

2: MOV DX,BWCOM

3: CHANGE:

4: LODSB

5: CMP AL,DL

6: JB CHANG1

7: CMP AL,DH

8: JA CHANG1

9: XOR AL,AH

10: MOV [SI-1],AL

11: CHANG1:

12: LOOP CHANGE

13: RET

以上介绍为变数法技巧,即将所要比较的值,放在暂存器中。由于暂存器快速、节省空间,因此程式效率高。更重要的一点,是程式本身的弹性大,只要应用方式统一,事先把参数设妥,即可共用。

四、回路中的指令

回路最重要的是速度,因为本段程式,将在计数器的范围之内,连续执行下去。如果不小心浪费了几个时钟值,在回路的累积下,很可能使程式成为牛步。

要想把回路写好,一定要记清楚每个指令的执行时钟,以便选择效率最高者。同时,要知道哪些指令可以获得相同的处理效果,才能有更多的选择。

其次,在回路中,最忌讳用缓冲器,不仅占用空间大,处理速度慢,而且不能灵活运用,功能有限。另外也应极力避免常数,尽量设法经由暂存器执行,用得巧妙时,常会将整个程式的效率提高百十倍。

还有便是少用 PUSH,POP,DIV,MUL和 CALL 等浪费时钟的指令。除此之外,小心、谨慎,深思、熟虑,才是把回路写好的不二法门。

在前例中,把比较常数的指令换为比较暂存器,便是很好的证明。如果用常数,两段程式决不可能共用,时、空都无谓地浪费了。

以下再举数例,乍看这似乎有些吹毛求疵,但是仔细计算一下所浪费的时间,可能就笑不出声了。

兹假定以下回路需处理五万字元的资料,频率为 10MHZ,其情况为:

1: LOOP1:

2: LODSB

3: XOR AL,[DI]

4: STOSB

5: LOOP LOOP1

本程式计数器等于50,000,每次需

12T+14T+11T+17T=55T 个时钟脉冲

若以50,000次计,需时 47*50,000/10,000,000 秒,即约四分之一秒。

只要稍稍将指令调整一下,为:

1: LOOP1:

2: LODSW

3: XOR AX,[DI]

4: STOSW

5: LOOP LOOP1

这样计数器只要25,000次,每次

16T+18T+15T+17T=66T

则25,000次需时 66*25,000/10,000,000 秒,约六分之一秒,比前面的程式快了二分之一。

同理,在回路中加回路,而每个回路需 17T,也是很大的浪费。倘若加调用 CALL 指令,则需 23T+20T=43T,浪费得更多,读者不可不慎。

当某一段程式用得很频繁时,理应视作子程式,例如下面的 LODAX:

1: LOOP1:

2: CALL LODAX

3: LOOP LOOP1

4: RET

5: LODAX:

6: LODSW

7: XOR AX,[DI]

8: STOSW

9: RET

其实这是贪小失大,仅四个字元的程式,竟用三个字元的调用指令去交换,是绝对得不偿失的。

再如同下面的程式,颇有值得商榷之处。

1: LOOP1:

2: MOV DX,NUMBER1

3: MOV CX,NUMBER2

4: LOOP2:

5: PUSH CX

6: MOV CX,DX

7: LOOP3:

8: LODSW

9: XOR AX,[DI]

10: STOSW

11: LOOP LOOP3

12: INC DI

13: INC DI

14: POP CX

15: LOOP LOOP2

16: RET

第二个回路是多余的,这是高阶语言常用的观念,对组合语言完全不适用。

稍加改动,不损上面程式原有的条件,得到:

1: LOOP1:

2: MOV DX,NUMBER1

3: LOOP2:

4: MOV CX,NUMBER2

5: LOOP3:

6: LODSW

7: XOR AX,[DI]

8: STOSW

9: LOOP LOOP3

10: INC DI

11: INC DI

12: DEC DX

13: JNZ LOOP2

14: RET

这样回路少了一个,程式中将5,6,14,15 各条中原来为15T+2T+12T+17T=46T的时间,省为12,13,14条的2T+16T+17T=35T。

第五节 分支处理

比较资料后,作条件分支 (Conditional Jump ),是程式中不可避免的手续。程式一长,分支距离超过 128个字元,条件分支就无法到达。当然,精简程式有时可以避免这种情形,但却不尽然。

处理条件分支的技术很多,其效率端视情况而定。最要紧的是事先规划,要比较些什么?在何种情况下?分支到哪里?做些什么工作等等。

不仅是写程式,人的各种能力,都可以由工作的方式判断出来。智慧高的人,很快就能抓住重点,再分门别类,钜细无遗的理出完整的系统。经过良好训练的专家,则能根据一套法规,逐步地整理归纳,也能推出合情合理的结果来。

老实说,电脑程式的写作技术还没有到成熟的阶段,当今所有的从业人员,都只能算是「拓荒者」,并没有真正的「专家学者」。充其量,像我个人一样,比别人机会好些,天天得以与电脑为伍,多一点经验而已。

因此,目前写程式几乎可以说没有可资遵循的法规,海阔天空,爱怎样写,就怎样写,只要能够使用,程式卖得出去,赚了大钱,就会被人视为大师。

只是这种情况维持不了多久了,初民的壁画,仅具有历史意义。今天的程式师,如果不认清现实,立刻觉醒,多致力于法规的制定,电脑将永远是个不成熟的孩子。一旦这些法规经得住考验,为未来的专家学者奠定基础,那才能真正的被视为大师。

我不讳言我们正朝着这个方向努力,但是,我却不认为做得到。因为电脑的硬体设计在今后的十年内,必然会有重大的突破,谁都难以预测会有什么结果。软体的制作观念虽然不可能有很大的改变,却难免会受到影响。只有各位年轻朋友,你们成长在电脑时代,肯多一分耕耘,必有收获!

下面,且介绍一些我对条件分支的处理技巧:

一、资料的分类

1,位元分类:

在本书第四章第五节所举的,由输入码作为输出字形的处理依据之例,就是采用位元分类的例证。

但凡以资料位元作为共同的分类讯息,而且各类皆有独特的处理方式者,皆应以其位元为顺序,用间接定址或分支技巧,作为程式处理之手段。

2,字元分类:

每一个字元具有 256种排列组合,设若有 128种以内的分类项目,应该取双数分类,否则须用连续分类。

分类之值,立即可以用间接定址执行。但须注意,各分类的入口标题应先行定义。由于定义必须用到双字元,所以,凡采用连续分类者,其值应乘二。

3,间隔分类:

在有些情况下,原有资料不容许重新安排,而且其中若干资料已具备分类之特性,这种情况,我们称之为间隔分类。

在处理此类资料时,应该先将可以作分类处理的资料提取出来,并视为字串,定义在一缓冲区内。当须要类比时,可利用「比对字串」 (SCAS) 的指令以求得其定义位置,再作间接定址。设有

4700H,4900H,4F00H,5100H,4A2DH,4EABH

等键盘输入数据。设上述值在AX中,需要作特殊处理,分别进入COD1至COD6等子程式。

11将资料定义在缓冲器 ABC中,程式则定义在DEF:

ABC DW 4700H,4900H,4F00H,5100H,4A2DH,4E2BH

DEF DW COD1,COD2,COD3,COD4,COD5,COD6

12使DI=ABC,CX=6:

MOV DI,OFFSET ABC

MOV CX,6

13由比对字串后,判断是否AX中有上述之值,如有,则用间接定址的方式执行之。

REPNZ SCASW ; 比对六组字串

JCXZ NOTHING ; 没有所比之字串

SUB DI,OFFSET ABC+2 ; 得到比对位置值

CALL CS:DEF[DI] ; 或作JMP

上述之DEF 如果放在DG段中,还可以节省一字元,并可加快速度:

CALL DEF[DI]

二、程式的结构

若在程式规划之初,未先做好准备工作,临时想用前述的方法,并非绝不可能。但是,东添一点,西补一段,这种程式不仅会导致测试的麻烦,更可能影响未来的维护和调整。

因此,每当了解了工作任务后,需要作间接定址的部份,最好能集中在一个模组内。万一性质不同必须分割,也应该将间接定址的程式,置放在模组的起头处。

这样做的好处很多,一方面便于扩充功能,每次增加定址因素时,不必在程式中寻来找去,立刻可以安排妥当。其次,这种定址的需求,必然与整体功能有关,而且定义表相当于一个目录,把纲领放在前面,按图索骥,一目了然。更重要的,是可以表现出程式结构的层次,层次处理是网状流程中最难以掌握的一环,不可不慎。

还有,就是各子程式的标题安排,其位置的先后应以功能的集中性为准。这样做的好处是,如果有可以共用的程式段,很容易就可合并为一,节省空间。

三、次序与条件「真」「假」

条件分支的「时钟数」有二个可能,条件符合时,执行分支为 16T,不符合则为 4T ,且继续下一指令。两者相差有四倍之多,我们正该利用这一特点,速度重要的条件,都应该设为主流程,否则为分流程。

尤其是在需要高速的回路中,分支处理得好坏,效率相去甚远。这种分支需要平时多加小心,培养出良好的习惯。

CDEF:

CMP AL,'?'

JZ ABCD ; 各比较符号中,'?' 者最少

LOOP CDEF ; NZ条件仅需4T速度较快

ABCD:

..

四、JMP 与 JMP SHORT

当程式师专心写作或侦错之时,常无法瞻前顾后。然而侦错完毕程式无误时,最好彻底检查一下所有的JMP 指令,经常会大有斩获!

因JMP 需要三字元,而JMP SHORT 只要两个,其条件是所跳越的位址不能超过128 字元。

在程式编译时,若向上JMP 的距离在 128字元以内,编译器会自动译为两字元。往下则不然,如在128 字元内,会再多加一个 NOP指令,不仅浪费一字元且多了两个时钟。

因此,细心检查一下,凡是向下跳,在128 字元以内,皆应改为JMP SHORT 才是。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有