组合语言之艺术
第一节 应用工具

一、对程序的认识
    写作程序不难,但要写出好程序却不容易。这就好象画图一样,人人都能画,而画
出来的图却可能有天壤之别。
    想作一个好画家,首先要有观察及分析的能力,面对着杂乱的事物,先整理出头绪,
找到主题。再在画布上勾出轮廓,这叫做「布局」。布局完毕,根据实际的环境,决定
作图的先后「顺序」。顺序是一种层次观念,景物及色彩都有一定的层次,绝不可随意
所之,想到哪里,画到哪里。
    观察考虑完毕,即开始准备,先将画笔、调色板等工具放妥,把要表现的主要色彩
也调好。最后是选择适当的画笔,蘸上色彩,按照所观察的结果,涂在画布上。
    画图颇重风格,有些个人主义的艺术家,技巧并不精通,只因为时代潮流或历史条
件,创造了某种独特的风格,就得以成名享利。一般的画家则不然,不论是「工笔」抑
或「写意」画,全靠其技巧及素养,始能求生存。至于艺术大师,则首重风格,再加上
素养、技巧,方可扬名立万,永垂不朽。
    最糟糕的画匠,既没有观察能力,更谈不上技巧和风格,除了照着别人的作品抄袭、
模仿外,创造不出有价值的作品。若程序员也如此,只能照着别人的意思,填填指令,
不过是个程序匠罢了。
    在观察分析之下,把欲表现的内容整理成为具体的步骤,用计算机术语来说,是为
「程序分析」,相当于画画中的「布局」。再下去,便是「流程」制作,或是作画的顺
序。将各种程序的层次安排妥当,才能开始写作程序,相当于开始作画。
    这些观念牵涉甚广,不是三言两语可以说完。本书仅以汇编语言写作的训练为目的。
如果读者能善用汇编语言的各种技巧,又能充份认识所要完成的工作,至少可以满足
「工笔画」的条件。对一个电脑程序而言,目前画「工笔画」的价值要比「写意」为高。
    下面,我们要以工笔画的立场,来理解汇编语言的应用。对油画或水彩画而言,色
料相当于程序用的「数据」,调色盘就是运用数据的「暂存器」,画笔等于「指令」,
一切都准备妥当,所谓「作画」就是「写程序」。
    程序是由一系列的定义和指令组织成的可执行的程序,需由一种档案的形式(.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
           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
     0     16     14     0EH
     1     17     15     0FH
   *10000     *20     16     *10H
     ★  凡前有 *者表示进位。
     ★★二进制数后应加'B',八进位后应加'O'。
    由上可知,十六进制仍沿用十进制数字,只是到了10时,已无现成数字可用,只好
借用英文字母。在程序中,汇编程序为了分辨ASCII 字符与十六进制数值,通常规定凡
十六进制数值以英文字母开始者,在其字母前加一'0'。
三、对寄存器的认识
    暂存器 (Register) 相当于调色皿,数据相当于色料。把色料放进调色皿里,为的
是要得到预定的效果,寄存器对于数据亦然。
    调色皿有大有小,深度有深有浅,其目的是针对不同的情况,以作有效的处理。寄
存器也是一样,应用得好,程序会很精简,容易修改、阅读。否则,想到哪一个就用哪
一个,没有原则,没有章法,这种程序委实不敢恭维。
    寄存器的重要性,在于处理方便灵活、速度快,占用空间小。不幸8088 CPU的寄存
器很少,用起来总是捉襟见肘,辛苦异常。正因为此,寄存器的善用与否,成为程序效
能高低的关键技术。
    有些程序员不愿意精打细算,经常设定一些「缓冲器」,利用缓冲器可以任意定名、
便于记忆的优点,竟把珍贵的暂存器,当作各缓冲器间、搬运数据的交通工具,只见数
据不停的搬进搬出。虽然程序员省了点事,但运行速度白白浪费了,空间也被糟蹋了。
写这样的组合程序,远不如去用高级语言。
    当然,缓冲器是有必要的,但也只限于「必要」的情况,而且,在程序规划时,就
要考虑各种应用的条件,把缓冲器内的值取出后,一次处理完毕。如果不能一次解决或
是经常要用到的数据,则设法放在寄存器中。
    实际上,任何程序不可能在一个过程中,同时需要很多特殊的数据。好的程序员能
把复杂的工作处理得有条不紊,功力不够的,往往把简单的事情弄得令人难以理解。80
88的寄存器的确是不够用,但是却不至于少到要以缓冲器取代的地步。
    工作的好坏、成败,与人的组织能力有绝对的关系,限于篇幅,我们不能多谈。可
是,利用寄存器的特性,来处理繁杂的数据,倒也是训练组织力的方法之一。
    首先,我们应该把寄存器视为工具,了解工具的功能、性质,然后要能铭记于心,
纯熟地加以运用。
    根据个人的理解,寄存器概分六类:
    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)。其书写方式与习惯的
由前到后正好相反,使用时要小心,其余细节请参看有关汇编语言手册。

上一页    下一页