来自于《Intel汇编语言程序设计》(第四版)第六章。
先引用原书的话来解释一下表格驱动分支选择:
表格驱动分支选择是使用表格查找法来替代多路选择结构的一种方法。为了使用该方法,我们必须创建一个包含查找值和过程偏移的表格,程序使用循环来搜索该表格,当需要大量的比较时,这种方法是工作的最好的。
下面是书中的例子程序,用户从键盘输入一个字符,程序使用一个循环将该字符同表中的每个子项比较,如果一个匹配项被发现,那么紧跟其后存储的过程地址将被调用。每个过程使用EDX来装入不同字符串的偏移,然后在循环中显示该字符串。
看概念总是感觉比较复杂,其实非常简单,看一下代码就全明白了。
原代码如下:
TITLE Table of Procedure Offsets (PorcTble.asm)
; This program contains a table with offsets of procedures.
; It uses the table to execute indirect procedure calls.
INCLUDE Irvine32.inc
.data
CaseTable BYTE 'A' ; look up value
DWORD Process_A ; address of procedure
EntrySize = ( $ - CaseTable )
BYTE 'B'
DWORD Process_B
BYTE 'C'
DWORD Process_C
BYTE 'D'
DWORD Process_D
NumberOfEntries = 4
prompt BYTE "Press capital A,B,C or D: ",0
msgA BYTE "Process_A",0
msgB BYTE "Process_B",0
msgC BYTE "Process_C",0
msgD BYTE "Process_D",0
.code
main PROC
mov edx,OFFSET prompt ; ask user for input
call WriteString
call ReadChar ; read character into AL
mov ebx,OFFSET CaseTable ; point EBX to the table
mov ecx,NumberOfEntries ; loop count
L1:
cmp al , [ebx] ; match found?
jne L2 ; no: continue
call NEAR PTR [ebx+1] ; yes : call the procedure
; CALL指令调用存储在 EBX+1 内存地址处的过程地址,这种间接调用格式要求使用NEAR PTR运算符
call WriteString ; display message
call Crlf
jmp L3 ; exit the search
L2:
add ebx,EntrySize ; point to the next entry
loop L1 ; repeat until ECX=0
L3:
exit
main ENDP
;下面的每个过程将一个不同的字符串偏移送至EDX
Process_A PROC
mov edx, OFFSET msgA
ret
Process_A ENDP
Process_B PROC
mov edx, OFFSET msgB
ret
Process_B ENDP
Process_C PROC
mov edx, OFFSET msgC
ret
Process_C ENDP
Process_D PROC
mov edx, OFFSET msgD
ret
Process_D ENDP
END main
以上便是所有代码。下面来逐步分析。
首先是数据段,进行了一些变量的定义,比较难理解的有可能是
EntrySize = ( $ - CaseTable )
这句,其实这个是为了计算出每个单元(一个字符+一个函数)的长度,这样在A结束之后减去CaseTable的首地址,就得到了一个单元的地址大小。
下面看一下代码段。让我们将main作为一个整体看一下:
main PROC
mov edx,OFFSET prompt ; 将要打印的提示用户输入的字符串移至edx寄存器,等待打印
call WriteString ; 打印EDX中的内容
call ReadChar ; 读取一个字符到AL寄存器中
mov ebx,OFFSET CaseTable ; 将EBX指向CaseTable首地址
mov ecx,NumberOfEntries ; 在ECX中保存要循环的值(这里为4)
L1:
cmp al , [ebx] ; 将用户输入的保存在AL中的字符串与CaseTable中的字符进行比较
jne L2 ; 如果不是,则跳转到L2
call NEAR PTR [ebx+1] ; 如果匹配,则会执行到这里,间接调用 EBX+1 地址处的过程
; CALL指令调用存储在 EBX+1 内存地址处的过程地址,这种间接调用格式要求使用NEAR PTR运算符
call WriteString ; 打印EDX寄存器中的值
call Crlf ; 将光标移动到下一行开始
jmp L3 ; 跳转到L3
L2:
add ebx,EntrySize ; 将EBX指向下一个单元
loop L1 ; 跳转到L1循环直到ECX=0
L3:
exit
main ENDP
这便是代码最主要的部分,余下的这些:
Process_A PROC
mov edx, OFFSET msgA
ret
Process_A ENDP
Process_B PROC
mov edx, OFFSET msgB
ret
Process_B ENDP
Process_C PROC
mov edx, OFFSET msgC
ret
Process_C ENDP
Process_D PROC
mov edx, OFFSET msgD
ret
Process_D ENDP
只是将要打印的字符串送至EDX等待调用WriteString进行打印而已。
以上便是所有代码讲解。
关于表格驱动分支选择,摘自原书:
表格驱动分支选择需要在初始化工作上有一些开销,但可以减少编写代码的数量。表格中可以包含大量的比较信息。而且这种方法比一系列很长的比较,跳转和调用指令的组合更加容易修改,表格甚至可以在运行时动态配置。