ITEEDU

助记符:TEST(Test)
用 途:测试寄存器或内存单元中一个位或几个位的0,1状态
格 式:TEST 寄存器,立即数
TEST 寄存器,寄存器
TEST 寄存器,存储单元
TEST 存储单元,立即数
TEST 存储单元,寄存器
执 行:CPU将源操作数与目的操作数相"与",但不保留结果,只是根据结果设置标志位

        disp  proc      near	;DISP子过程
              push      bx	;保存寄存器
              push      si
              push      di
              mov       cx,16	;处理32个字节(16个字)
              mov       si,offset buffer	;SI寄存器指向文件缓冲区
              mov       dx,100	;汉字左上角的Y坐标送入DX寄存器
      loop2:
              push      cx	;暂存CX寄存器中的计数值
              mov       bx,8000h	;将"掩模"送入BX寄存器
              mov       cx,152	;汉字左上角的X坐标送入CX寄存器
              mov       di,16	;处理16个Bit
			
              lodsw     ;取得一个字
              xchg      ah,al	;交换高低字节
      loop3:
              test      ax,bx	;测试"掩模"中指定的数位
              push      ax	;暂存待测数据
              jz        next_dot	;若所测试的数位为0,转NEXT_DOT继续
              mov       ah,0ch	;画一个洋红色的点
              mov       al,2
              int       10h
   next_dot:
              inc       cx	;X坐标加1,准备处理下一个数位
              shr       bx,1	;BX寄存器中的"掩模"右移1位
              pop       ax	;恢复AX寄存器中的待测数据
              dec       di	;已处理了全部16个数位了吗?
              jnz       loop3	;未处理完全部数位,转LOOP3继续
              inc       dx	;Y坐标加1,准备处理下一条线
              pop       cx	;恢复CX寄存器中的计数值
              loop      loop2	;转LOOP2继续
			
              pop       di	;恢复寄存器
              pop       si
              pop       bx
              ret       ;返回主过程
        disp  endp
        code  ends
              end       main

 这个程序和第三章的PROG4程序有一点相象,程序PROG4是利用AND指令进行按位测试工作的。实质上CPU执行TEST指令和执行AND指令是一样的,即把待测试的寄存器与"掩模"数据做一次"与"操作,根据结果设置标志位,但"与"之后的结果不保留。TEST与AND的关系类似于CMP与SUB的关系,将这样两个指令联合记忆效果不错。当然,也可以按以下规律记忆:

(1)TEST指令后的源操作数为被测试数据,目的操作数为"掩模",需要测试哪几个数位,"掩模"中相应的数位就要置1。
(2)若被测试的数位都是0,那么ZF标志将被置1,指令"JZ"可以完成转移,反之如果被测数位中有一个不是0,ZF标志就会清0,指令"JNZ"可以完成转移。仔细观察"Z/NZ"与被测数位之间的关系可以很好地掌握TEST指令。

数据的分析看来不是很困难,那么如何获得字模数据呢?所有的汉字操作系统都带有标的字模库,如UCDOS所带的HZK16(HZK16F为繁体)。如果能够搞清这些字模库的结构,就可以从这些字模库中提取出汉字字模数据。为此要先搞清汉字的编码方法。

GB-2312-80标准一共收录了6763个常用的汉字和682个全角符号,一共是7445个字符。这些文字和符号是分区存放的,下面给出的这个表就是第1区内收录的字符和第16区内收录的汉字,可以看出一个区内共有汉字94个,为了便于汉字的检索,我们根据汉字所在的区号与这个汉字在所在区中的偏移位置为每个汉字编了码,比如"啊"字是第16区的第一个字,所以编码为1601;"白"字是第16区的第55个字,所以编码为1655;同理"剥"字编码为1694。

由于这种编码方法是根据某个汉字的"区"与"位"而来,所以这种编码一般称为"区位"码,左面两位称"区码",右面两位称"位码"。根据汉字的区位码我们就可以计算出某个汉字在整个字库中的偏移位置,公式为"(区码-1) 94+位码"。

区位码仅仅用于汉字的检索,真正在机器内部并不用区位码表示汉字。我们都清楚凡是字符在机器内部都是用一些数字表示的,用于表示字符的这些数字就称为字符的编码。在PC系统中西文字符最带用的编码系统为ASCII码,每一个字符对应一个字节的数据。那么如何用数字表示汉字呢?

最容易想到的一个方案就是直接用区位码表示汉字。这确实是个方案,不过它并不可行。可以举个很简单的例子来说明:比如从键盘输入一个字符串"HELLO!"主机就会从键盘接收到这样一串数字--72、69、76、76、79、33(用十进制表示)。如果汉字是用区位码表示的话,对于这样一串数字计算机就可以有两种理解,或者将其理解为"HELLO!",或者将其理解为"桢袄锪"。计算机究竟响应哪一个呢?除非我们给计算机足够的智能使其自己能判断"桢袄锪"没有意义,否则计算机将无所适从。

实际上给汉字进行编码还是有规律可循的。首先我们应该明确下面这样一些事实:
(1)汉字的内码必须由两个字节组成;中文字符共有七千多个,必须用两个字节才能无重复地表示所有中文字符。
(2)汉字的内码不能占用标准打字键盘上所有西文字符的ASCII码,而且汉字的编码应该容易与西文字符区分;
(3)汉字的内码应该可以用某种算法转换为对应的区位码;因为区位码可以在国标汉字库中检索汉字。

这是三项最基本的规律,根据这三点我们来看看汉字的实际编码方案是怎样的。

组成汉字内码的两个字节其最高位都为1,即两个数字都大于127(也可以理解为两个都是负数,无所谓)。西文字符都集中于ASCII码表的前半部分,即基本ASCII码表之中,汉字内码采用了扩展ASCII码,这样即可做到不与西文字符相冲突,而且也便于区分汉字与西文字符。

具体地说这表示汉字的两个字节都是从0A1H开始,表8-1给出了汉字库中最前面两区全角字符的内码,可以看到内码的低字节可以用于区分汉字所在的区,而高字节可以区分汉字在其所在区中的位置。

这样的内码是很容易转换成区位码的,"区码=内码低字节-0A0H,位码=内码高字节-0A1H"。由此可见这种编码方式是符合前述三个基本原则的,不过它也有一些弊病,比如西文制表符在中文系统中往往被显示成一些汉字。好在这不是主要矛盾,把"┌─"当成"谀"总比把"HELLO!"当成"桢袄锪"要强。

结合前面讲的利用区位码计算汉字在整个汉字库中偏移量的公式,即可得出通过汉字内码计算汉字在字库中的偏移量的方法,即:(内码低字节-0A1H)x94+(内码高字节-0A1H)。

表9-1 汉字库中部分字符与汉字的内码
区位	内码	字符	区位	内码	字符	区位	内码	字符	区位	内码	字符
0102	0A2A1	、		0401	0A1A4	ぁ		1601	0A1B0	啊		8785	0F5F7	黪
0103	0A3A1	。		0402	0A2A4	あ		1602	0A2B0	阿		8786	0F6F7	黯
0104	0A4A1	·		0403	0A3A4	ぃ		1603	0A3B0	埃		8787	0F7F7	鼢
0105	0A5A1	ˉ		0404	0A4A4	い		1604	0A4B0	挨		8788	0F8F7	鼬
0106	0A6A1	ˇ		0405	0A5A4	ぅ		1605	0A5B0	哎		8789	0F9F7	鼯
0107	0A7A1	¨		0406	0A6A4	う		1606	0A6B0	唉		8790	0FAF7	鼹
0108	0A8A1	〃		0407	0A7A4	ぇ		1607	0A7B0	哀		8791	0FBF7	鼷
0109	0A9A1	々		0408	0A8A4	え		1608	0A8B0	皑		8792	0FCF7	鼽
0110	 0AAA1	―		 0409	0A9A4	ぉ		 1609	0A9B0	癌		 8793	 0FDF7	 鼾
0111	0ABA1	~		0410	0AAA4	お		1610	0AAB0	蔼		8794	0FEF7	齄

知道了某个汉字在字库中的偏移量,又知道一个16 16点的汉字其字模数据为32个字节,就能计算出这个汉字的字模数据在字模库文件中的偏移量,从而可以利用DOS提供的移动文件读写指针的功能定位文件指针并读出字模数据。DOTMAP程序显示的第一个汉字是"文",它的内码是"0C4CEH",根据刚刚给出的公式,可以计算出它的字模数据在字模库文件HZK16中的偏移位置为"[(0CEH-0A1H) 94+(0C4H-0A1H)] 32=21520H",这就是程序中"OFF"数组中定义的第一个数据。下面这个程序就是由DOTMAP程序改进而得,用以验证上述的算法:

DOTMAP2.ASM

        data  segment
              assume    ds:data
       fname  db        'hzk16',0	;UCDOS的显示字模库文件
      buffer  db        32 dup(0)	;文件缓冲区
       hanzi  db        '文化,中国'	;待显示的5个汉字字符
         msg  db        Press any key to display the next font.',0dh,0ah,24h
        data  ends
        code  segment
              assume    cs: code
        main  proc      far
              mov       ax,data	;初始化DS寄存器
              mov       ds,ax
			
              mov       dx,offset fname	;打开字模库文件
              mov       ax,3d00h
              int       21h
              jc        exit	;若文件操作出错,转EXIT结束进程
			
              mov       cx,5	;显示5个汉字字符
              mov       bx,ax	;文件读写指针送入BX寄存器
              mov       si,offset hanzi	;SI寄存器指向汉字串
      loop1:
              push      cx	;暂存CX寄存器中的计数值
              mov       ax,0004h	;设置图形显示模式4
              int       10h
			
              mov       ah,09h	;显示字符串
              mov       dx,offset msg
              int       21h
			
              lodsw     ;取得一个汉字的内码
              sub       ax,0a1a1h	;高低字节都减去0A1H
              mov       dx,ax	;暂存汉字位码
              mov       cl,94	;计算(区码-0A1H)x94
              mul       cl
              mov       dl,dh	;将DH寄存器中的位码扩至16位
              mov       dh,0
              add       ax,dx	;计算(区码-0A1H)x94+位码
              mov       cx,32	;计算汉字字模在字模库中的偏移量
              mul       cx
              mov       cx,dx	;将这个偏移量送入CX-DX寄存器中
              mov       dx,ax
			
              mov       ah,42h	;移动文件读写指针
              mov       al,00h
              int       21h
              jc        exit	;若文件操作出错,转EXIT结束
			
              mov       ah,3fh	;读入32字节字模数据
              mov       dx,offset buffer
              mov       cx,32
              int       21h
              jc        exit	;若文件操作出错,转EXIT结束
			
              call      disp	;调用DISP子过程显示汉字
			
              mov       ah,0	;等待键盘输入
              int       16h
			
              pop       cx	;恢复CX寄存器中的计数值
              loop      loop1	;转至LOOP1继续
       exit:
              mov       ax,0003h	;设置字符显示模式3
              int       10h
			
              mov       ah,4ch	;结束进程
              int       21h
        main  endp
        disp  proc      near
			;DISP子过程与DOTMAP.ASM相同,此处省略
        disp  endp
        code  ends
              end       main