施 志 刚
(南通航运职业技术学院 信息系,江苏 南通 226010)
摘 要:本文通过具体实例分析了汇编语言程序设计过程中的若干常见错误,包括语法错误和程序设计上的逻辑错误,旨在帮助读者在程序设计时能够避免本文提出的错误。
关键词:分析;汇编语言;语法错误;逻辑错误
Conmmon Errors Analysis In Assembly Language
Shizhigang
(IT Department, Nantong Vocational & Technical Shipping College, Nantong Jiangsu 226010)
Abstract: This article analysised conmmon errors in assembly language programming process through the concrete examples,including grammer errors and logic errors in programming,aimed at helping readers to prevent from the errors that this article refered to in programming.
Key words: analysis; assembly language; grammer errors; logic errors
0 引言
汇编语言是计算机能够提供给用户使用的最快而又最有效的语言,也是能够利用计算机所有硬件特性并能直接控制硬件的惟一语言。因而对程序的空间和时间要求很高的场合,汇编语言的应用是必不可少的。至于很多需要直接控制硬件的应用场合,则更是非用汇编语言不可了。作为计算机专业及相关学科一门重要的基础课程,其重要性不言而喻。但是对于众多初学汇编语言的人来说,都普遍感到较难且不易理解和掌握,难以入门,看着有错的程序,更是不知从何改起,本人在汇编语言的教学过程中积累并总结了一些这方面的经验,以供各位读者借鉴和参考。
1 汇编语言常见错误
1.1语法错误
1.1.1 数据类型不一致
在汇编语言中,对于双操作数指令(比如mov,add等),规定两操作数的数据类型必须一致,所谓数据类型的一致是指两操作数必须同为字节或者同为字类型的数据。例如:
Data segment
s1 db 11h,22h
s2 dw 3344h,7788h
Data ends
:
Mov ax,s1+1
Mov al,s2
汇编程序在汇编这一段程序时,能发现两条mov指令的两个操作数的类型属性是不同的:s1+1为字节型属性,而ax为字类型属性;s2为字类型属性,而al为字节类型属性。因此,汇编程序将指示出错:两条mov指令中的两个操作数的类型不匹配。这时可以指定操作数的类型属性,即使用PTR属性操作符,它赋予变量一个新的临时类型属性,类型可以是byte,word等,其本身并不分配存储单元。因此,第一条mov指令可以改成:mov ax,word ptr s1+1,这样就把s1+1的类型属性指定为字,两个操作数的属性也就一致了,汇编时不会出错。同理,第二条mov指令可以写成mov al,byte ptr s2。
1.1.2双操作数指令中两操作数同为存储器单元
在汇编语言中,规定双操作数指令中两操作数不能同为存储器单元,必须有一个是寄存器。例如:
Data segment
s1 dw 1,2,3
s2 dw 10 dup(?)
Data ends
:
Mov s2+6,s1+2
汇编语言在汇编这段程序时,能发现mov指令中的两操作数同为存储器单元。s1和s2是在数据段定义的两个变量,类型均为字。dw伪指令为其后跟着的数据存入指定的存储单元,形成初始化数据;或者只分配存储空间而并不存入确定的数值,形成未初始化数据,例如dup。这里s1和s2是两个连续存储空间的起始地址,代表两个存储单元。因此要在两个存储单元间传送数据,可以借助于寄存器。该条mov指令可以改成:
Mov ax,oper1+2
Mov oper+6,ax
1.1.3使用未定义的变量
在汇编语言中,要求对所有用到的变量作强制定义,即“先定义,后使用”。初学者经常在程序的执行部分直接使用没有定义过的变量。例如:
Data segment
s1 db 1,2,3
Data ends
:
Mov cx,3
Mov si,0
Mov al,0
Next:Add al,[s1+si]
Inc si
Loop next
Mov sum,al
:
不难看出,上述程序段实现的是1+2+3,并最终把结果存入sum单元中。但是汇编语言在汇编时,发现指令中用到的变量sum在数据段中并没有定义,于是报错使用了未定义的变量。这主要表现为初学者编写程序的随意性,对在程序中可能要用到的变量,没有很好的预见性和判断力。要使汇编通过,只要在数据段中为sum预留一个字节的存储空间就可以了,即增加一条伪指令语句sum db ?。
1.1.4标号引用了指令的操作码
标号是可执行语句的符号地址,常常作为jmp,call和loop等指令的目的操作数,是指令地址的符号表示。使用时后面必须跟有冒号,尤其要注意标号不能引用指令的操作码,比如add,sub,loop等。例如:
Data segment
s1 db 1,2,3
Data ends
:
Mov cx,3
Mov si,0
Mov al,0
add:Add al,[s1+si]
Inc si
Loop add
:
还是上文提到的那个程序段,注意其中的标号next改成了add。这样一改,肯定通不过汇编了,原因就在于标号引用了指令的操作码。Loop是循环指令,它的指令格式是loop opr,其中opr为转向的目标地址,即标号,在上述指令序列中就是add。明显和加法指令add同名了,这时只要把标号add改成add1即可以通过汇编。因此,对于初学者,今后在编写程序时,尤其要注意标号不能引用指令的操作码。
1.1.5指针寄存器未正确赋值之前就引用
在汇编语言中没有定义指针,但在8086/8088微处理器中有一些专用寄存器,比如bx,bp,si和di,它们是用来存放地址的,通过地址实现对存储单元内容的访问。而在c语言中定义了指针,它用来存放要访问的内存单元的地址,同样都存放地址,于是暂且可以把bx,bp,si和di同c语言中的指针关联起来,称为地址指针寄存器,这样就为理解和编写程序带来了极大的方便。地址指针寄存器通常用于循环程序设计中,每执行完一次循环前都要及时修改地址指针,让其指向下一个将要访问的存储单元的地址。因此在设计循环程序时,都需要给在主程序中出现的地址指针寄存器赋初值。例如:
Data segment
0per1 db 1,2,3
Data ends
:
Mov cx,3
Mov al,0
next:Add al,[oper1+si]
Inc si
Loop next
:
上述指令序列,貌似看也是实现1+2+3。在主程序中出现了指针寄存器si,并且一次循环结束前,也用inc si及时修改了指针值。但在循环的初始状态中并未对si赋初值,造成循环语句无法正常运行。因此对于初学者,尤其要注意对将要在循环体中出现的指针寄存器在程序的开始赋初值。
1.2逻辑错误
1.2.1循环语句中未规定循环结束条件
在汇编语言中,循环程序通常由三部分组成:循环的初始状态、循环体和循环控制部分。初学者在设计循环程序时,经常忘记循环的初始化或忘记规定循环结束的条件,从而造成循环语句无法正常运行。因此合理地选择结束条件就成为循环程序设计的关键问题。通常设定循环结束条件有两种方法:
A.如果循环次数是已知的,可以用循环次数作为循环的控制条件。在循环程序中,cx就是用来专门存放循环次数的寄存器。例如:
Data segment
s1 db 1,2,3
Data ends
:
Mov cx,3
Mov si,0
Mov al,0
next:Add al,[s1+si]
Inc si
Loop next
:
在上述程序段中,循环体要被执行3次,这时可以在循环开始前使用mov cx,3。在一次循环结束时通过loop next来实现(cx)-1→(cx),如果(cx) 0,进入第二次循环,依次下去,直到(cx)=0退出循环。
B.若循环次数未知,就需要根据具体情况找出控制循环的结束条件。
例如:设计一段程序求自然数的和,若1+2+3……<500,停止运算,并把结果存放在sum单元中。
该题可以用循环程序来设计,但循环次数是未知的,这时就要找出控制循环结束的条件,即1+2+3……<500。指令序列如下:
Data segmeng
Sum dw ?
Data ends
:
Mov ax,0
Mov bx,1
Next:Add ax,bx
Inc bx
Cmp ax,500
Jb next
Mov sum,ax
:
在上述程序段中,Cmp ax,500就是用来控制循环的。只要(ax)<500,循环体中的指令被重复执行,否则就跳出循环,并将最终的结果存放在sum中。
对于初学者,由于对循环次数不能正确预见或无意识设置,并且循环结束条件设定也不正确,这些都会导致程序结果输出的不正确性。在这里重点提出,关键在于引起读者的重视。
1.2.2循环语句中循环控制变量无变化
循环程序中初始状态主要包括设置控制循环的次数,设置与循环有关的地址指针等。这里提到的循环控制变量无变化实际上就是地址指针无变化。一次循环结束前,都要在循环体中修改有关的地址指针值,为进入下次循环做好保障。下面把上文1+2+3的程序段通过一存储空间图给大家作简要描述。在执行第一次循环体时,地址指针寄存器si指向1所在的存储单元,如图1-(a)所示,在循环体中执行了加法指令后,要及时修改地址指针si,即执行inc si,在判断了(cx) 0后,随即进入第二次循环,这时si指向2,如图1-(b)所示,加法指令执行完,同时修改si,判断(cx) 0,进入第三次循环,这时si指向3,如图1-(c)所示,在执行完加法指令和修改了si后,判断(cx)=0,便跳出循环,程序执行完毕。
如果把指令inc si去掉,则指针si永远指向1所在的存储单元,即实现1+1+1。因此一次循环结束前要及时修改地址指针的值。si开始指向1,执行了inc si后,指针si便指向2所在的存储单元,依次下去,直至程序执行完毕。初学者在设计循环程序时,经常忘记改变循环控制变量的值,从而造成循环语句无法正常运行,影响最终结果的正确性。
1.2.3lea和mov指令使用不正确
Mov是数据传送指令,传送存储单元的数据;而lea是地址传输指令,负责传送存储单元的偏移地址。区别很明显,使用过程中却发现很多问题,例如:
Data segment
s1 db 1,2,3
Data ends
:
Mov cx,3
Mov si,0
Mov al,0
next:Add al,[s1+si]
Inc si
Loop next
:
上述程序段中,si是地址指针寄存器,存放存储单元的偏移地址。程序中s1对应存储单元的偏移地址初始化为0,存放在si中,因此mov si,0可以改成lea si,s1。若改成mov si,s1,意义就完全不同了。这时si中存放的就不是s1对应存储单元的地址了,而是s1对应存储单元的内容,运行结果显然不正确。若把mov si,0改成lea si,s1,相当于s1对应存储单元的偏移地址被符号化了。这时指令中还有一处也必须改过来,才能保证程序的正确性,即next:后的指令应改为add al,[si]。因此,初学者尤其要注意在阅读和编写程序时,不要混淆这两条指令,并在程序设计中正确使用。
1.2.4jmp指令的不正确使用
在程序中有时需要针对不同的情况或条件做出不同的处理,这样的程序就要采用分支结构。分支程序常常利用转移指令来实现。但程序中如果有多路分支,实现比较复杂时,初学者往往就会忽视JMP指令,致使程序不能无条件跳转到另一个分支,当然汇编能通过,却输出不正确的结果。下面的程序段用来统计正数和负数的个数:
Data segment
S1 db 1,-2,-1,-5,3,9,-7,5
Data ends
:
Mov cx,8
Lea bx,s1
Mov ax,0
Next:cmp [bx],0
Jae next1
Next2:inc ah
Next1:inc al
Inc bx
Loop next
:
上述的程序段通过汇编后得出正数个数不正确,关键就是没用好jmp指令。在循环体中,首先通过Cmp [bx],0把bx对应存储单元中的1和0比较,执行条件转移指令jae next1将程序转到标号为next1的指令处执行,即执行inc al,用来统计正数的个数,然后执行inc bx,让bx指向-2,一次循环结束。进入第二次循环,执行Cmp [bx],0,这时bx对应存储单元的数据是-2,即把-2和0比较。显然-2<0,不满足条件转移指令jae next1,于是顺序执行标号为next2处的指令inc ah,用来统计负数的个数。问题来了,按理说负数统计好了,此次循环也该结束了,只要执行inc bx,让指针寄存器bx加1,指向-1,进入下一次循环。但程序在执行完next2处的指令inc ah后,不会跳到inc bx处执行,而是顺序执行next1处的指令inc al,即先让统计正数个数的寄存器al加1,然后再执行inc bx,很明显这是错误的。此循环中-2是负数,al不应加1。因此要使汇编后得到正确的结果,只要在inc ah后面增加一无条件转移指令jmp next3,并同时在inc bx的前面加一个标号next3:即可,在这里把jmp指令提出来,旨在帮助初学者在设计分支程序时要注意程序的跳转并用好jmp指令。
1.2.5在子程序中未正确对寄存器保护和恢复
初学者在设计子程序时往往忽视寄存器的保存与恢复,从而得到不正确的结果。因此在编写程序时要用到子程序,应仔细考虑哪些寄存器是必须保存的,哪些寄存器是不必要或不应该保存的。寄存器的保存和恢复分别用PUSH和POP指令。一般子程序中用到的寄存器是要保存的。但是,有时主程序在调用子程序时,经常需要向子程序传递一些参数;子程序在执行完后也需要向主程序返回一些数据。因此,用来在主程序和子程序间传递参数的寄存器不能保存,否则就破坏了应该向主程序传送的信息。
下面的一段程序实现将一个2位十进制数的压缩bcd码,转换成二进制数。指令序列如下:
Data segment
Bcdz db 00110100b
Binz db ?
:
Mov al,bcdz
Call sub1
Mov binz,al
:
Sub1 proc near
Pushf
Push bx
Push cx
Mov ah,al
And ah,0fh
Mov bl,ah
And al,0f0h
Mov cl,4
Ror al,cl
Mov bh,0ah
Mul bh
Add al,bl
Pop cx
Pop bx
Popf
Ret
Sub1 endp
:
上述程序段在子程序中使用的al和ah,最终保存的结果是要传递给主程序的,因此在子程序中不能用push指令对ax进行保存,否则就会破坏向主程序传送的信息。初学者在设计子程序时,尤其要明确子程序需要向主程序传递哪些参数,而保存这些参数的寄存器在子程序中就不能保护,其他不需向主程序传递参数的寄存器,只要在子程序中用到就必须保存和恢复。
2 结束语
汇编语言作为一门专业基础课,其重要性不言而喻。当然在使用汇编语言编写程序时,经常遇到的错误将远远超出本文所提到的这些,本文作者只是作了简要的归纳,旨在通过这篇文章,希望读者今后在学习汇编语言的过程中,少走弯路,并善于总结经验,尽量避免错误的发生。
参考文献
[1]沈美明,温冬婵.IBM-PC汇编语言程序设计[M].北京:清华大学出版社,2001.
[2]沈美明,温冬婵,张赤红. IBM-PC汇编语言程序设计实验教程[M].北京:清华大学出版社,2002.
[3]顾元刚,韩雁,易顺明.汇编语言与微机原理教程[M].北京:电子工业出版社,2005.
[4]王富荣,刘造新,娄智.汇编语言[M].北京:人民交通出版社,2004.下载本文