汇编语言实例:矩阵行求和

前面《二维数组简介》一节中已经展示了如何计算字节矩阵中单个行的总和。尽管这个解决方案有些冗长,现在还是要看看能否用宏来简化任务。首先,我们来回顾一下 calc_row_sum 过程:
;------------------------------------------------------------
; calc_row_sum
; 计算字节矩阵中一行的和数
; 接收: EBX = 表偏移量, EAX = 行索引
;       ECX = 按字节计的行大小
; 返回:  EAX 为和数
;------------------------------------------------------------
calc_row_sum PROC uses ebx ecx edx esi

    mul     ecx                             ; 行索引 * 行大小
    add     ebx,eax                         ; 行偏移量
    mov     eax,0                           ; 累加器
    mov     esi,0                           ; 列索引

L1:    movzx edx,BYTE PTR[ebx + esi]        ; 取一个字节
    add     eax,edx                         ; 与累加器相加
    inc     esi                             ; 行中的下一个字节
    loop L1

    ret
calc_row_sum ENDP
从把 PROC 改为 MACRO 开始,删除 RET 指令,把 ENDP 改为 ENDM。由于没有宏与 USES 伪指令功能相当,因此插入 PUSH 和 POP 指令:
mCalc_row_sum MACRO
    push ebx              ;保存被修改的寄存器
    push ecx
    push esi
    mul ecx               ;行索引x行大小
    add ebx,eax           ;行偏移量
    mov eax,0             ;累加器
    mov esi,0             ;列索引
L1: movzx edx,BYTE PTR[ebx + esi]
    add eax, edx          ;取一个字节
    inc esi               ;与累加器相加
    loop L1               ;行内的下一个字节
    pop esi               ;恢复被修改的寄存器
    pop ecx
    pop ebx
ENDM
接着,用宏参数代替寄存器参数,并对宏内寄存器进行初始化:
mCalc_row_sum MACRO index, arrayoffset, rowSize
    push ebx                       ;保存被修改的寄存器
    push ecx
    push esi
;设置需要的寄存器
    mov eax,index
    mov ebx,arrayOffset
    mov ecx,rowSize
    mul ecx                        ;行索引x行大小
    add ebx,eax                    ;行偏移量
    mov eax,0                      ;累加器
    mov esi,0                      ;列索引
L1: movzx edx, BYTE PTR[ebx + esi] ;取一个字节
    add eax, edx                   ;与累力口器相力口
    inc esi                        ;行内的下一个字节
    loop L1
    pop esi                        ;恢复被修改的寄存器
    pop ecx
    pop ebx
ENDM
然后,添加一个参数 eltType 指定数组类型 (BYTE、WORD 或 DWORD):

mCalc_row_sum MACRO index, arrayOffset, rowSize, eltType

复制到 ECX 的参数 rowSize 现在表示的是每行的字节数。如果要用其作为循环计数器,那么它就必须转换为每行的元素 (element) 个数。

因此,若为 16 位数组,就将 ECX 除以 2 ;若为双字数组,就将 ECX 除以 4。实现上述操作的快捷方式为:eltType 除以 2,把商作为移位计数器,再将 ECX 右移:

shr ecx,(TYPE eltType/2)          ; byte=0, word=1, dword=2

TYPE eltType 就成为 MOVZX 指令中基址-变址操作数的比例因子:

movzx edx,eltType PTR[ebx + esi*(TYPE eltType)]

若 MOVZX 右操作数为双字,那么指令不会汇编。所以,当 eltType 为 DWORD 时,需要用 IFIDNI 运算符另外编写一条 MOV 指令:

IFIDNI <eltType>,<DWORD>
    mov edx,eltType PTR[ebx + esi*(TYPE eltType)]
ELSE
    movzx edx, eltType PTR[ebx + esi*(TYPE eltType)]
ENDIF

最后必须结束宏,记住要把标号 L1 指定为 LOCAL:
;-----------------------------------------------------
mCa1c_row_sum MACRO index, arrayOffset, rowSize, eltType
;计算二维数组中一行的和数。
;接收:行索引、数组偏移量、每行的字节数、数组类型 (BYTE、WORD、或 DWORD)。
;返回:EAX= 和数。
;-----------------------------------------------------
LOCAL L1
    push ebx                    ;保存被修改的寄存器
    push ecx
    push esi
;设置需要的寄存器
    mov eax, index
    mov ebx, arrayOffset
    mov ecx, rowSize
;计算行偏移量
    mul ecx                      ;行索引x行大小
    add ebx, eax                 ;行偏移量
;初始化循环计数器
    shr ecx,(TYPE eltType/2)     ;byte=0, word=1, dword=2
;初始化累加器和列索引
    mov eax,0                    ;累加器
    mov esi,0                    ;列索引
L1:
    IFIDNI <eltType>, <DWORD>
        mov edx,eltType PTR[ebx + esi*(TYPE eltType)]
    ELSE
        movzx edx,eltType PTR[ebx + esi*(TYPE eltType)]
    ENDIF
    add eax,edx                  ;与累加器相加
    inc esi
    loop L1
    pop esi                      ;恢复被修改的寄存器
    pop ecx
    pop ebx
ENDM
下面用字节数组、字数组和双字数组对宏进行示例调用:
.data
tableB BYTE 10h, 20h, 30h, 40h, 50h
RowSizeB = ($ - tableB)
    BYTE 60h, 70h, 80h, 90h, 0A0h
    BYTE 0B0h, 0C0h, 0D0h, 0E0h, 0F0h

tableW WORD 10h, 20h, 30h, 40h, 50h
RowSizeW = ($ - tableW)
    WORD 60h, 70h, 80h, 90h, 0A0h
    WORD 0B0h, 0C0h, 0D0h, 0E0h, 0F0h

tableD DWORD 10h, 20h, 30h, 40h, 50h
RowSizeD = ($ - tableD)
    DWORD 60h, 70h, 80h, 90h, 0A0h
    DWORD 0B0h, 0C0h, 0D0h, 0E0h, 0F0h

index DWORD ?
.code
mCalc_row_sum index, OFFSET tableB, RowSizeB, BYTE
mCalc_row_sum index, OFFSET tableW, RowSizeW, WORD
mCalc_row_sum index, OFFSET tableD, RowSizeD, DWORD

编程帮,一个分享编程知识的公众号。跟着站长一起学习,每天都有进步。

通俗易懂,深入浅出,一篇文章只讲一个知识点。

文章不深奥,不需要钻研,在公交、在地铁、在厕所都可以阅读,随时随地涨姿势。

文章不涉及代码,不烧脑细胞,人人都可以学习。

当你决定关注「编程帮」,你已然超越了90%的程序员!

编程帮二维码
微信扫描二维码关注