助记符: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)。
区位 内码 字符 区位 内码 字符 区位 内码 字符 区位 内码 字符 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