本篇记录《汇编语言:基于X86处理器》第9章 编程练习的学习笔记.
9.10 编程练习
下面的练习可以用32位模式或64位模式完成。所有字符串处理过程都假设使用的是空字符结束的字符串。即使没有明确的要求,每道练习都需编写简单的驱动程序来测试所实现的新过程。
*1.改进Str_copy过程
本章给出的Str_copy过程没有限制被复制字符的数量。编写新过程(名为Str_copyN)再接收一个输入参数,表示被复制字符数的最大值。
;9.10_1.asm 9.10 编程练习 *1.改进Str_copy过程
;本章给出的Str_copy过程没有限制被复制字符的数量。
;编写新过程(名为Str_copyN)再接收一个输入参数,表示被复制字符数的最大值。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
src BYTE "Good night Tom1",0
tgt BYTE "hello", 24 DUP(0), 0 ;30字节空间
.code
;-------------------------------------------
;将字符串从源串复制到目的串。
;要求:目标串必须有足够空间容纳从源复制来的串。
;返回:无
;--------------------------------------------
Str_copyN PROC USES eax ecx esi edi,
source:PTR BYTE, ;source string
target:PTR BYTE, ;target string
copyCount:DWORD ;复制的字符数
mov ecx, copyCount ;重复计数器
mov esi, source
mov edi, target
;把edi定位到tgt字符串的最后位置
L1: mov al, [edi]
cmp al, 0
je L2
inc edi
jmp L1
L2:
cld ;方向为正向
rep movsb ;复制宇符串 [esi]=>[edi] source复制到target尾部
ret
Str_copyN ENDP
main PROC
mov ebx, LENGTHOF tgt ;30字节空间
INVOKE Str_copyN, ADDR src, ADDR tgt, 10
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
复制后:
** 2.Str_concat 过程
编写过程 Str_concat 将源串连接到目的串的末尾。目的串必须有足够的空间来容纳新字符。传递源串和目的串的指针。示例调用如下所示:
.data
targetStr BYT "ABCDE", 10 DUP(0)
sourceStr BYTE "FGH",0
.code
INVOKE Str_concat, ADDR targetStr, ADDR scourceStr
完整的代码如下:
;9.10_2.asm 9.10 编程练习 ** 2.Str_concat 过程
;编写过程 Str_concat 将源串连接到目的串的末尾。
;目的串必须有足够的空间来容纳新字符。传递源串和目的串的指针。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
targetStr BYTE "ABCDE", 10 DUP(0),0
sourceStr BYTE "FGH",0
.code
;------------------------------------------
;获取字符串的长度。
;返回值,EAX返回字符串的长度
;------------------------------------------
Str_length PROC USES edi,
pString:PTR BYTE
mov edi, pString ;指向字符串
mov eax, 0 ;字符计数器
L1: cmp BYTE PTR[edi], 0 ;字符结束?
je L2 ;是:退出
inc edi ;否:指向下一个字符
inc eax ;计数器加1
jmp L1
L2: ret
Str_length ENDP
;-------------------------------------------
;将字符串从源串复制到目的串。
;要求:目标串必须有足够空间容纳从源复制来的串。
;返回:无
;--------------------------------------------
Str_concat PROC USES eax ecx esi edi,
target:PTR BYTE,
source:PTR BYTE
INVOKE Str_length, target ;获取字符串的长度,保存在EAX中
mov edi, target
;把edi定位到tgt字符串的最后位置
add edi, eax
mov esi, source
INVOKE Str_length, source ;获取字符串的长度,保存在EAX中
mov ecx, eax ;重复计数器
cld ;方向为正向
rep movsb ;复制宇符串 [esi]=>[edi] source复制到target尾部
ret
Str_concat ENDP
main PROC
mov ebx, LENGTHOF targetStr ;30字节空间
INVOKE Str_concat, ADDR targetStr, ADDR sourceStr
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
连接后:
**3.Str_remove 过程
编写过程 Str_remove从字符串中删除n个字符。需传递参数为:被删除字符在串中的位置指针,以及定义删除字符数量的整数。例如,下面的代码展示了如何从target中删除“xxxx”:
.data
target BYTE "abcxxxxdefghijklmop",0
.code
INVOKE Str_remove, ADDR [target+3], 4
完整的代码:
;9.10_3.asm 9.10 编程练习 **3.Str_remove 过程
;编写过程 Str_remove从字符串中删除n个字符。需传递参数为:被删除字符在串中的位置指针,
;以及定义删除字符数量的整数。例如,下面的代码展示了如何从target中删除“xxxx”:
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
target BYTE "abcxxxxdefghijklmop",0
.code
;-------------------------------------------
;从字符串中删除n个字符
;接收:删除的起始位置,删除的个数
;返回:无
;--------------------------------------------
Str_remove PROC USES eax ecx esi edi,
tgt:PTR BYTE, ;target string
removeCount:DWORD ;移除的字符数
mov ecx, removeCount ;重复计数器
mov edi, tgt ;起始位置
mov esi, tgt
add esi, removeCount
;[esi]==>[edi]
;cld ;方向为正向
;rep movsb ;[edi+removeCount]=>[edi] 字符串前移abcdefgdefghijklmop
;abcdefghijklmoplmop
L1: mov al, [esi]
cmp al, 0
je L2
mov [edi],al
inc esi
inc edi
jmp L1
;abcdefghijklmoplmop=====>abcdefghijklmop
L2: dec esi
mov BYTE PTR [esi], 0
loop L2
ret
Str_remove ENDP
main PROC
mov ebx, LENGTHOF target
INVOKE Str_remove, ADDR target+3, 4
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
删除后:
***4.Str_find 过程
编写过程 Str_find在目的串中查找第一次出现的源串,并返回其位置。输入参数为源串指针和目的串指针。如果查找成功,过程将零标志位ZF置1,用EAX指向目的串的匹配位置。否则,ZF清零,EAX无定义。例如,下面的代码查找“ABC”,并用EAX返回“A”在目的串中的位置:
.data
target BYTE "123ABC342432", 0
source BYTE "ABC", 0
pos DWORD ?
.code
INVOKE Str_find, ADDR source, ADDR target
jnz notFound
mov pos, eax ;保存位置值
完整的代码如下:
;9.10_4.asm 9.10 编程练习 ***4.Str_find 过程
;编写过程Str_find在目的串中查找第一次出现的源串,并返回其位置。
;输入参数为源串指针和目的串指针。如果查找成功,过程将零标志位ZF置1,
;用EAX指向目的串的匹配位置。否则,ZF清零,EAX无定义
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
target BYTE "123ABC342432", 0
source BYTE "ABC", 0
pos DWORD ?
.code
;-----------------------------------------------------
;从目标串中查找源串。
;接收: source:源字符串的指针,target:目的字符串的指针
;返回: 返回的位置保存到EAX中
;-----------------------------------------------------
Str_find PROC USES ecx esi edi,
src:PTR BYTE, ;source string
tgt:PTR BYTE ;target string
mov edi, tgt
mov esi, src
mov ebx, 0
;查找
L1: mov al, [esi + ebx]
cmp [edi], al
je L2 ;相等
inc edi ;不相等,查找下一个字符
mov al, [edi]
cmp al, 0
je L4 ;目标字符串到结尾,没有找到子串
mov ebx, 0 ;子串从初始位置开始
jmp L1
L2: inc edi
inc ebx
mov al, [esi+ebx]
cmp al, 0
je L3 ;子串在目标字符串中查找结果
mov al, [edi]
cmp al, 0
je L4 ;目标字符串到结尾,没有找到子串
jmp L1
L3: ;查找成功,将零标志位ZF置1
xor eax, eax ;将寄存器与自身异或,结果为 0,ZF=1
mov eax, ebx ;保存位置到eax中
jmp quit
L4: ;没有找到子串,将零标志位ZF置0
or eax, 0FFFFFFFEh
quit:
ret
Str_find ENDP
main PROC
mov ebx, LENGTHOF target
INVOKE Str_find, ADDR source, ADDR target
jnz notFound
mov pos, eax ;保存位置值
notFound:
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
把子串改为“ABCD”,再次运行调试测试:
**5.Str_nextWord 过程
编写过程Str_nextWord扫描字符串,查找第一次出现的指定分隔符,并将其替换为空字节输入参数有两个:字符串指针和分隔符。调用之后,如果发现分隔符,零标志位ZF置1,EAX为分隔符下一个字符的偏移量。否则,ZF清零,EAX无定义。下面的示例代码传递了target的指针和分隔符“,”:
.data
target BYTE "Johnson,Calvin",0
.code
INVOKE Str_nextWord, ADDR target, ','
jnz notFount
如图9-5所示,调用StrnextWord后,EAX指向了被发现(并替换)的“,”后面的那个字符。
;9.10_5.asm 9.10 编程练习 **5.Str_nextWord 过程
;编写过程Str_nextWord扫描字符串,查找第一次出现的指定分隔符,
;并将其替换为空字节输入参数有两个:字符串指针和分隔符。
;调用之后,如果发现分隔符,零标志位ZF置1,EAX为分隔符下一个字符的偏移量。
;否则,ZF清零,EAX无定义。下面的示例代码传递了target的指针和分隔符“,”:
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
target BYTE "Johnson,Calvin",0
pos DWORD ?
.code
;-----------------------------------------------------
;查找第一次出现的指定分隔符,并将其替换为空字节输入参数有两个:字符串指针和分隔符。
;接收: tgt:目的字符串的指针,
;返回: 返回的位置保存到EAX中
;-----------------------------------------------------
Str_nextWord PROC,
tgt:PTR BYTE, ;target string
val:BYTE
mov edi, tgt
;查找
L1: mov al, [edi]
cmp al, 0
je L4 ;到字符串末尾了还没有找到分隔符
cmp al, val
je L2 ;相等跳转到L2
inc edi ;不相等,查找下一个字符
jmp L1
L2: mov BYTE PTR [edi], ' ' ;将分隔符替换为空字符
inc edi
;查找成功,将零标志位ZF置1
xor eax, eax ;将寄存器与自身异或,结果为 0,ZF=1
jmp quit
L4: ;没有找到子串,将零标志位ZF置0
or eax, 0FFFFFFFEh
quit:
ret
Str_nextWord ENDP
main PROC
mov ebx, LENGTHOF target
INVOKE Str_nextWord, ADDR target, '?'
jnz notFound
movzx eax, BYTE PTR [edi] ;eax指向分隔符的下一个字符
notFound:
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
把分隔符换成?号测试:
**6.构建频率表
编写过程 Get_frequencies构建一个字符频率表。需向过程输人字符串指针,以及一个数组指针,该数组包含 256个双字,并已初始化为全0。每个数组位置都由其对应字符的ASCII码进行索引。过程返回时,数组中的每一项包含的是对应字符在串中出现的次数。例如,
.data
target BYTE "AAEBDCFBBC", 0
freqTable DWORD 256 DUP(0)
.code
INVOKE Get_frequencies, ADDR target, ADDR freqTable
图9-6展示了一个字符串和频率表的表项41(十六进制)到4B。位置41包含数值2,原因是字母A(ASCII码为41h)在字符串中出现了两次。其他字符也进行相同的计数。频率表常用于数据压缩和其他涉及字符处理的应用。例如,哈夫曼(Huffman)编码算法中,与较不常用的字符相比,最常出现字符的保存位数更少。
;9.10_6.asm 9.10 编程练习 **6.构建频率表
;编写过程 Get_frequencies构建一个字符频率表。需向过程输人字符串指针,
;以及一个数组指针,该数组包含 256个双字,并已初始化为全0。
;每个数组位置都由其对应字符的ASCII码进行索引。过程返回时,
;数组中的每一项包含的是对应字符在串中出现的次数。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
target BYTE "AAEBDCFBBC", 0
freqTable DWORD 256 DUP(0)
.code
;-------------------------------------------
;构建一个字符频率表
;接收:目标字符串, 频率表
;返回:无
;--------------------------------------------
Get_frequencies PROC USES ebx esi edi,
tgt:PTR BYTE, ;target string
freq:PTR BYTE ;频率表
mov esi, tgt
mov edi, freq
;根据[esi]中的值为索引,找频率表的位置
L1: movzx ebx, BYTE PTR [esi] ;esi里的值为频率表索引值
cmp ebx, 0
je L2 ;等于0表示字符串结束
shl ebx, 2 ;相当于ebx * 4, 频率表的位置是32位的
inc DWORD PTR[edi+ebx] ;[edi + index*4]
inc esi
jmp L1
L2:
ret
Get_frequencies ENDP
main PROC
mov ebx, LENGTHOF target
INVOKE Get_frequencies, ADDR target, ADDR freqTable
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
调用后:
***7.厄拉多塞过滤算法
厄拉多塞(Eratosthenes)过滤算法,由同名的希腊数学家发明,提供了在给定范围内快速查找所有质数的方法。该算法创建一个字节数组,并按如下方式在“被标记”位置上插入1:从位置2(2是质数)开始,则数组中所有2的倍数的位置都插入1。接着,对下一个质数3,用同样的方法处理3的倍数。查找3之后的质数,该数为5,再对所有5的倍数的位置进行标记。持续这种操作直到找出质数的全部倍数。那么,剩下数组中没有被标记的位置就表示其数为质数。编写程序,创建一个含有65000个元素的数组,并显示2到65000之间的所有质数。要求,在未初始化数据段中声明该数组(参见3.4.1节),且使用STOSB把0填充到数组中。
;9.10_7.asm 9.10 编程练习 ***7.厄拉多塞过滤算法
;.386
;.model flat, stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORD
INCLUDE Irvine32.inc
.data
src BYTE 65000 DUP(?) ;65000字节空间
.code
;-------------------------------------------
;厄拉多塞(Eratosthenes)过滤算法标记素数。
;接收:source:字节数指针, count数组元素个数。
;返回:无
;--------------------------------------------
Eratosthenes PROC USES ebx ecx esi edi,
source:PTR BYTE,
count:DWORD
mov edi, source ;数组的指针
mov ecx, count ;元素个数
mov ebx, 2 ;质数从2开始
mov esi, 2
L1:
mov bl, BYTE PTR[edi + esi]
cmp bl, 1 ;被标记过了,不是素数
je next ;判断下一个数
mov ebx, 2 ;从2开始判断是否有被整除的
L2:
cmp ebx, esi
je prime ;相等说明前面都没有被整除,该数是素数
mov edx, 0 ;高位清0
mov eax, esi ;被除数放在eax中
div ebx ;商在eax中,余数在edx中
cmp edx, 0
je next ;等于0, 有被整除,不是素数,检查下一个数
cmp ebx, esi
jl next2 ;ebx比esi小,再除下一个数
jmp next
next2:
inc ebx
jmp L2
prime:
mov eax, esi ;打印素数call WriteDec call Crlf 换行
;在数组中标记素数
mov BYTE PTR [eax + edi], 0
;eax的倍数都不是素数,要标记为1
L3: add eax, esi
cmp eax, count ;判断有没有超出范围
ja next ;大于跳转到next
mov BYTE PTR [eax + edi], 1 ;标记为1
jmp L3
next:
inc esi
loop L1
ret
Eratosthenes ENDP
main PROC
;初始化字符串
mov al, 0 ;要保存的数值
mov edi, OFFSET src ;EDI指向目标字符串
mov ecx, LENGTHOF src ;字符计数器
cld ;方向为正
rep stosb ;用AL的内容实现填充
INVOKE Eratosthenes, ADDR src, LENGTHOF src
;打印素数, 从2开始
mov esi, OFFSET src
mov ecx, LENGTHOF src
sub ecx, 2
mov ebx, 2
mov edi, 0
prime:
movzx eax, BYTE PTR [esi+ebx]
cmp eax, 1
je next
mov eax, ebx
call WriteDec
;一行输出12个质数
cmp edi, 12
je L1
mov al, 9 ;输出tab符
call WriteChar
jmp L2
L1:
call Crlf ;换行
mov edi, 0
jmp next
L2:
inc edi
next:
inc ebx
loop prime
call Crlf
INVOKE ExitProcess,0
main ENDP
END main
运行调试:
调用Eratosthenes过程后,标记素数:
输出素数:
*8.冒泡排序
向 9.5.1节的 BubbleSort 过程添加一个变量,进行内循环时,只要有一对数值交换就将其置1.若在某次遍历过程中发现没有数值交换,就在过程正常结束之前,利用该变量提前退出。(该变量通常被称为交换标志(exchange flag)。)
;9.10_8.asm 9.10 编程练习 *8.冒泡排序
.386
.model flat, stdcall
.stack 4096
BubbleSort PROTO, pArray:PTR DWORD, Count:DWORD, isChange:PTR DWORD
ExitProcess PROTO,dwExitCode:DWORD
.data
tableD DWORD 100h, 20h, 3030h, 402h, 50h, 80h, 6070h, 6020h
isXchg DWORD 0
.code
main PROC
mov esi, OFFSET tableD
INVOKE BubbleSort, OFFSET tableD, LENGTHOF tableD, OFFSET isXchg
INVOKE ExitProcess, 0
main ENDP
;------------------------------------------
;使用冒泡算法,将一个32位有符号整数数组按升序进行排列
;接收:数组指针,数组大小, 交换判断标志
;返回:无
;------------------------------------------
BubbleSort PROC USES eax ecx esi,
pArray:PTR DWORD, ;数组指针
Count:DWORD, ;数组大小
isChange:PTR DWORD ;用来判断内循环是否有交换
mov ecx, Count
dec ecx ;计数值减1
L1: push ecx ;保存外循环计数值
mov esi, pArray ;指向第一个数值
L2: mov eax, [esi] ;取数组元素值
cmp [esi + 4], eax ;比较两个数值
jg L3 ;如果[ESI]<=[ESI+4],不交换
xchg eax, [esi+4] ;交换两数
mov [esi], eax
mov [isChange], 1
L3: add esi,4 ;两个指针都向前移动一个元素
loop L2 ;内循环
pop ecx ;恢复外循环计数值
cmp [isChange], 0 ;判断内循环是否有交换
je L4 ;没有交互,结束循环,说明已经排好序
loop L1 ;若计数值不等于0,则继续外循环
L4: ret
BubbleSort ENDP
END main
运行调试:
排序后:
**9.对半查找
重新编写本章给出的对半查找过程,用寄存器来表示mid、first 和last。添加注释说明寄存器的用法。
;9.10_9.asm 9.10 编程练习 **9.对半查找
;重新编写本章给出的对半查找过程,用寄存器来表示mid、first 和last。添加注释说明寄存器的用法。
;.386
;.model flat, stdcall
;.stack 4096
INCLUDE Irvine32.inc
.data
array DWORD 12h, 30h, 52h, 64h, 78h, 93h, 97h
.code
;------------------------------------------
;BinarySearch
;在一个有符号整数数组中查找某个数值。
;接收:数组指针、数组大小、给定查找数值
;返回:若发现匹配项,则EAX=该匹配元素在数组中的位置;否则,EAX=-1。
;------------------------------------------
BinarySearch PROC USES ebx edx esi edi,
pArray:PTR DWORD, ;数组指针
Count:DWORD, ;数组大小
searchVal:DWORD ;给定查找数值
mov esi, pArray
mov ecx, Count
mov ebx, 0 ;ebx=first=0(数组起始索引)
;mov edx, Count
;dec edx ;edx = last = Count-1 (数组结束索引)
lea edx, [ecx - 1] ;返回间接操作数的地址 其原理返回实现的有效地址 7 - 1 = 6
;lea edx, [ecx] ;这里有个现象,如果ecx是值,返回的是这个ecx的值, 7, 其实质是[ecx+0]
;lea edx, [esi] ;如果esi是地址,返回的就是esi的址址 00404000h 其实质是[esi+0] esi本身的值是00404000h
;lea edx, [50h] ;edx = 50h
;lea edx, [00404060h] ;edx = 00404060h
searchLoop:
cmp ebx, edx ;比较first和last
jg notFount ;如果first>last,未找到
;计算mid = (first + last) / 2
mov edi, ebx ;edi=first
add edi, edx ;edi=first+last
shr edi, 1 ;edi=(first+last)/2
;比较array[mid]和搜索值
mov ecx, [esi + edi*4] ;ecx = array[mid] 数组元素为DWORD型
cmp eax, ecx
je found ;相等,找到元素
jl searchLeft ;如果搜索值 < array[mid],搜索左半部分
;否则搜索右半部分
searchRight:
lea ebx, [edi+1] ; first = mid + 1
jmp searchLoop
searchLeft:
lea edx, [edi - 1] ; last = mid - 1
jmp searchLoop
found:
mov eax,edi ;返回找到的索引(mid)
jmp done ;返回 mid
notFount:
mov eax,-1 ;查找失败
done:
ret
BinarySearch ENDP
main PROC
mov eax, 52h
;实现对半查找 用EAX返回
INVOKE BinarySearch, ADDR array, LENGTHOF array, eax
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
***10.字母矩阵
编写过程生成一个4x4的矩阵,矩阵元素为随机选择的大写字母。选择字母时,必须保证被选字母是元音的概率为 50%。编写测试程序,用循环调用该过程5次,并在控制台窗口显示所有矩阵。前三次矩阵的示例输出如下所示:
元音字母有五个,分别是 a、e、i、o、u。
原理,先用RandomRange生成元音或辅音的随机会,再根据随机数,生成元音的随机数,或辅音的随机会。如果随机数为0,调用RandomRange生成元音随机数,如果随机数为1生成辅音的随机数。
RandomRange过程在范围0~n-1内生成一个随机整数,其中n是用EAX寄存器传递的输入参数。生成的随机数也用EAX返回。
;9.10_10.asm 9.10 编程练习 ***10.字母矩阵
;编写过程生成一个4x4的矩阵,矩阵元素为随机选择的大写字母。
;选择字母时,必须保证被选字母是元音的概率为 50%。
;编写测试程序,用循环调用该过程5次,并在控制台窗口显示所有矩阵。
INCLUDE Irvine32.inc
.data
matrixSize equ 16 ;4x4 matrix
vowels BYTE 'AEIOU', 0 ;元音列表
consonants BYTE 'BCDFGHJKLMNPQRSTVWXYZ', 0 ;辅音列表
matrix BYTE 16 dup(0) ;存储生成的矩阵
.code
;-------------------------------------------
;生成矩阵
;要求:目标串必须有足够空间容纳从源复制来的串。
;返回:无
;--------------------------------------------
GenerateMatrix PROC USES eax ebx ecx edx esi edi,
source:PTR BYTE, ;source string
vowel:PTR BYTE, ;元音列表
consonant:PTR BYTE, ;辅音列表
copyCount:DWORD ;复制的字符数
mov ecx, copyCount ;重复计数器
mov esi, source
mov edi, 0
mov ebx, 0
;生成随机数
L1: mov eax, 2 ;生成的范围在0或1
call RandomRange ;生成的随机数保存在eax中
cmp eax, 0
je L2 ;如果eax==0,接下来生成随机数,取元音字符
;否则取辅音字母,辅音字母有21个,因此序号在0~20
mov eax, 21 ;设置随机数范围
call RandomRange ;生成随机数0~20
;mov al, BYTE PTR consonants[eax] ;取辅音字母
mov ebx, consonant
mov al, BYTE PTR[ebx+eax]
mov BYTE PTR[esi+edi], al ;把辅音字母存入矩阵中
jmp next
L2: ;元音字母只有5个,因此序号在0~4
mov eax, 5 ;设置随机数范围
call RandomRange ;生成随机数0~4
;mov al, BYTE PTR vowels[eax] ;取元音字母
mov ebx, vowel
mov al, BYTE PTR[ebx+eax]
mov BYTE PTR [esi+edi], al ;把元音字母存入矩阵中[esi+edi]
next:
inc edi
loop L1
;打印4 * 4矩阵
mov ecx, copyCount
mov ebx, 0
L3:
mov al, BYTE PTR[esi+ebx]
call WriteChar
inc ebx
mov edx, 0
mov eax, ebx
mov edi, 4
div edi ;余数在edx中
cmp edx, 0
je L4
mov al, 9 ;tab符号
call WriteChar
jmp nextChar
L4:
call Crlf ;换行
nextChar:
loop L3
ret
GenerateMatrix ENDP
main PROC
mov ecx, 5
mov ebx, LENGTHOF matrix
L1:
INVOKE GenerateMatrix, ADDR matrix, ADDR vowels, ADDR consonants, matrixSize
call Crlf
loop L1
nop
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
生成矩阵:
输出:
****11.字母矩阵/按元音分组
本程序以上一道编程练习生成的字母矩阵为基础。生成一个4x4的字母矩阵,其中每个字母都有 50%的概率为元音字母。遍历矩阵的每一行,每一列,和每个对角线,并产生字母组。当一组四个字母中只有两个元音字母时,显示该字母组。比如,假设生成矩阵如下所示:
则程序应显示的四字母组为POAZ、GKAE、IAGD、PAGI、ZUED、PEAD和ZAKI。各组内字母的顺序并不重要。
;9.10_11.asm 9.10 编程练习 ****11.字母矩阵/按元音分组
;本程序以上一道编程练习生成的字母矩阵为基础。生成一个4x4的字母矩阵,
;其中每个字母都有 50%的概率为元音字母。遍历矩阵的每一行,每一列,和每个对角线,
;并产生字母组。当一组四个字母中只有两个元音字母时,显示该字母组。
INCLUDE Irvine32.inc
.data
matrixSize equ 16 ;4x4 matrix
vowels BYTE 'AEIOU', 0 ;元音列表
consonants BYTE 'BCDFGHJKLMNPQRSTVWXYZ', 0 ;辅音列表
matrix BYTE 16 dup(0),0 ;存储生成的矩阵
tempList BYTE 4 DUP(?), 0 ;临时列表
.code
;-------------------------------------------
;生成矩阵
;要求:目标串必须有足够空间容纳从源复制来的串。
;返回:无
;--------------------------------------------
GenerateMatrix PROC USES eax ebx ecx edx esi edi,
source:PTR BYTE, ;source string
vowel:PTR BYTE, ;元音列表
consonant:PTR BYTE, ;辅音列表
copyCount:DWORD ;复制的字符数
mov ecx, copyCount ;重复计数器
mov esi, source
mov edi, 0
mov ebx, 0
;生成随机数
L1: mov eax, 2 ;生成的范围在0或1
call RandomRange ;生成的随机数保存在eax中
cmp eax, 0
je L2 ;如果eax==0,接下来生成随机数,取元音字符
;否则取辅音字母,辅音字母有21个,因此序号在0~20
mov eax, 21 ;设置随机数范围
call RandomRange ;生成随机数0~20
;mov al, BYTE PTR consonants[eax] ;取辅音字母
mov ebx, consonant
mov al, BYTE PTR[ebx+eax]
mov BYTE PTR[esi+edi], al ;把辅音字母存入矩阵中
jmp next
L2: ;元音字母只有5个,因此序号在0~4
mov eax, 5 ;设置随机数范围
call RandomRange ;生成随机数0~4
;mov al, BYTE PTR vowels[eax] ;取元音字母
mov ebx, vowel
mov al, BYTE PTR[ebx+eax]
mov BYTE PTR [esi+edi], al ;把元音字母存入矩阵中[esi+edi]
next:
inc edi
loop L1
;ret ;返回,下面的打印输出不执行
;打印4 * 4矩阵
mov ecx, copyCount
mov ebx, 0
L3:
mov al, BYTE PTR[esi+ebx]
call WriteChar
inc ebx
mov edx, 0
mov eax, ebx
mov edi, 4
div edi ;余数在edx中
cmp edx, 0
je L4 ;4个数换一行
mov al, 9 ;tab符号
call WriteChar
jmp nextChar
L4:
call Crlf ;换行
nextChar:
loop L3
ret
GenerateMatrix ENDP
;查找有2个元音字母, 就打印
FindVowel PROC USES ebx ecx edi esi,
vowel:PTR BYTE, ;元音列表
count:DWORD
mov esi, vowel
mov ecx, count
mov edi, offset tempList
mov eax, 0
L1: push ecx
mov ebx, 0
mov dl, BYTE PTR [edi] ;取列表的字母
mov ecx, 5
L2:
cmp dl, BYTE PTR [esi+ebx]
jne nextVowel ;不相等对比下一个元音字母
inc eax ;相待,统计数量
jmp nextChar
nextVowel:
inc ebx
loop L2
nextChar:
pop ecx
inc edi ;下一个字母比较
loop L1
cmp eax, 2
jne quit ;不等于2就退出
mov edx, offset tempList ;等于2打打印4个字母
call WriteString
call Crlf
quit:
ret
FindVowel ENDP
VowelArray PROC USES eax ebx ecx edi esi,
source:PTR BYTE ;矩阵
mov esi, source
;遍历行
mov ecx, 4
mov edi, OFFSET tempList
rowL1:
push ecx
mov ebx, 0
mov ecx, 4
rowL2:
mov al, BYTE PTR [esi+ebx]
mov BYTE PTR [edi+ebx], al ;把一行字母都放到tempList
inc ebx
loop rowL2
;1行数据4个字母存放完成
INVOKE FindVowel, ADDR vowels, 5
add esi,4 ;下一行数
pop ecx
loop rowL1
call Crlf
;遍历列
mov esi, source
mov ecx, 4
colL1:
push ecx
mov ebx, 0
mov ecx, 4
mov edi, OFFSET tempList
colL2:
mov al, BYTE PTR [esi+ebx]
mov BYTE PTR [edi], al ;把一行字母都放到tempList
add ebx, 4
inc edi
loop colL2
;1列数据4个字母存放完成
INVOKE FindVowel, ADDR vowels, 5
add esi,1 ;下一列数
pop ecx
loop colL1
call Crlf
;遍历对角线
;matrix[0][0], matrix[1][1], matrix[2][2], matrix[3][3]
mov ecx, 4
mov ebx, 0
mov esi, source
mov edi, OFFSET tempList
L1:
mov al, BYTE PTR [esi+ebx]
mov BYTE PTR [edi+ebx], al
inc ebx
add esi, 4
loop L1
INVOKE FindVowel, ADDR vowels, 5
;matrix[0][3], matrix[1][2], matrix[2][1], matrix[3][0]
mov ecx, 4
mov ebx, 3
mov esi, source
mov edi, OFFSET tempList
L2:
mov al, BYTE PTR [esi+ebx]
mov BYTE PTR [edi+ebx], al
dec ebx
add esi, 4
loop L2
INVOKE FindVowel, ADDR vowels, 5
ret
VowelArray ENDP
main PROC
mov ecx, 1
mov ebx, LENGTHOF matrix
L1:
INVOKE GenerateMatrix, ADDR matrix, ADDR vowels, ADDR consonants, matrixSize
call Crlf
loop L1
nop
INVOKE VowelArray, ADDR matrix
INVOKE ExitProcess, 0
main ENDP
END main
运行结果:
**** 12.数组行求和
编写程序 calc_row_sum计算二维的字节数组、字数组或双字数组中单行的总和。过程需有如下堆栈参数:数组偏移量、行大小、数组类型、行索引。返回的和数必须在EAX中。要求使用不能用INVOKE或扩展的PROC。编写程序,分别用字节数组、字数组和双字数显式堆栈参数,组来测试过程。要求用户输人行索引,并显示被选择行的和数。
;9.10_12.asm 9.10 编程练习 **** 12.数组行求和
;编写程序 calc_row_sum计算二维的字节数组、字数组或双字数组中单行的总和。
;过程需有如下堆栈参数:数组偏移量、行大小、数组类型、行索引。
;返回的和数必须在EAX中。要求使用不能用INVOKE或扩展的PROC。
;编写程序,分别用字节数组、字数组和双字数显式堆栈参数,组来测试过程。
;要求用户输人行索引,并显示被选择行的和数。
INCLUDE Irvine32.inc
.data
; 测试数组
byteArray BYTE 1, 2, 3, 4
BYTE 5, 6, 7, 8
BYTE 9, 0Ah, 0Bh, 0Ch
byteRows = 3
byteCols = 4
wordArray WORD 10h, 20h, 30h, 40h
WORD 50h, 60h, 70h, 80h
WORD 90h, 100h, 110h, 120h
wordRows = 3
wordCols = 4
dwordArray DWORD 100h, 200h, 300h, 400h
DWORD 500h, 600h, 700h, 800h
DWORD 900h, 1000h, 1100h, 1200h
dwordRows = 3
dwordCols = 4
prompt1 BYTE "Enter row index (0-based): ", 0
prompt2 BYTE "Sum of selected row: ", 0
typePrompt BYTE "Testing array type: ", 0
byteType BYTE "BYTE", 0
wordType BYTE "WORD", 0
dwordType BYTE "DWORD", 0
.code
main PROC
; 测试字节数组
mov edx, OFFSET typePrompt
call WriteString
mov edx, OFFSET byteType
call WriteString
call Crlf
call GetUserInput
push eax ; 行索引
push 1 ; 数组类型: 1=BYTE
push byteCols ; 行大小(列数)
push OFFSET byteArray ; 数组偏移量
call calc_row_sum
call DisplayResult
; 测试字数组
call Crlf
mov edx, OFFSET typePrompt
call WriteString
mov edx, OFFSET wordType
call WriteString
call Crlf
call GetUserInput
push eax ; 行索引
push 2 ; 数组类型: 2=WORD
push wordCols ; 行大小(列数)
push OFFSET wordArray ; 数组偏移量
call calc_row_sum
call DisplayResult
; 测试双字数组
call Crlf
mov edx, OFFSET typePrompt
call WriteString
mov edx, OFFSET dwordType
call WriteString
call Crlf
call GetUserInput
push eax ; 行索引
push 4 ; 数组类型: 4=DWORD
push dwordCols ; 行大小(列数)
push OFFSET dwordArray ; 数组偏移量
call calc_row_sum
call DisplayResult
INVOKE ExitProcess, 0
main ENDP
; 获取用户输入的行索引
GetUserInput PROC
mov edx, OFFSET prompt1
call WriteString
call ReadInt
ret
GetUserInput ENDP
; 显示结果
DisplayResult PROC
mov edx, OFFSET prompt2
call WriteString
call WriteInt
call Crlf
ret
DisplayResult ENDP
; 计算行总和的函数
; 参数顺序(从最后一个开始push):
; 1. 数组偏移量
; 2. 行大小(列数)
; 3. 数组类型(1=BYTE, 2=WORD, 4=DWORD)
; 4. 行索引(0-based)
calc_row_sum PROC
push ebp
mov ebp, esp
push ebx ; 保存寄存器
push ecx
push edx
push esi
mov esi, [ebp+8] ; 数组偏移量
mov ecx, [ebp+12] ; 行大小(列数)
mov edx, [ebp+16] ; 数组类型
mov eax, [ebp+20] ; 行索引
; 计算行起始地址 = 数组基址 + (行索引 * 行大小 * 元素大小)
imul eax, ecx ; 行索引 * 列数
imul eax, edx ; 再乘以元素大小
add esi, eax ; ESI现在指向行的起始地址
xor eax, eax ; 清空EAX用于累加和
; 根据数组类型选择循环处理方式
cmp edx, 1
je sum_bytes
cmp edx, 2
je sum_words
cmp edx, 4
je sum_dwords
sum_bytes:
movsx ebx, BYTE PTR [esi]
add eax, ebx
add esi, 1
loop sum_bytes
jmp done
sum_words:
mov bx, WORD PTR [esi]
add eax, ebx
add esi, 2
loop sum_words
jmp done
sum_dwords:
add eax, DWORD PTR [esi]
add esi, 4
loop sum_dwords
done:
pop esi ; 恢复寄存器
pop edx
pop ecx
pop ebx
pop ebp
ret 16 ; 清理16字节的堆栈参数
calc_row_sum ENDP
END main
运行调试:
***13.裁剪前导字符
编写Str_trim过程的变体,使得主调程序能从字符串中删除所有的前导字符。比如,若调用过程时,有一指针指向字符串“###ABC”,且向过程传递了字符“#”,则结果字符串应为“ABC”。
;9.10_13.asm 9.10 编程练习 ***13.裁剪前导字符
;编写Str_trim过程的变体,使得主调程序能从字符串中删除所有的前导字符。
;比如,若调用过程时,有一指针指向字符串“###ABC”,
;且向过程传递了字符“#”,则结果字符串应为“ABC”。
INCLUDE Irvine32.inc
.data
string_1 BYTE "###Hello",0
string_2 BYTE "###Hello#World",0
string_3 BYTE "Hello",0
string_4 BYTE "###H@$#&ello#",0
string_5 BYTE "#Hello###",0
.code
;------------------------------------------
;获取字符串的长度。
;返回值,EAX返回字符串的长度
;------------------------------------------
StrLengthA PROC USES edi,
pString:PTR BYTE
mov edi, pString ;指向字符串
mov eax, 0 ;字符计数器
L1: cmp BYTE PTR[edi], 0 ;字符结束?
je L2 ;是:退出
inc edi ;否:指向下一个字符
inc eax ;计数器加1
jmp L1
L2: ret
StrLengthA ENDP
;------------------------------------------
;显示字符串。
;返回:无
;------------------------------------------
ShowString PROC pString1:PTR BYTE
mov edx, pString1
call WriteString
call Crlf
ret
ShowString ENDP
;------------------------------------------
;从字符串头部删除所有与给定分隔符匹配的字符。
;返回:无
;------------------------------------------
StrTrim PROC USES eax ebx ecx edi,
pStr:PTR BYTE, ;指向字符串
char:BYTE ;要移除的字符
mov edi, pStr ;数组指针
INVOKE StrLengthA, edi ;用EAX返回长度
cmp eax, 0 ;长度是否为零?
je quit ;是:立刻退出
mov esi, pStr
mov ebx, 0
L1: mov al, [edi+ebx]
cmp al, char ;比较字符
jne quitLoop ;不相等退出循环
inc ebx ;比较下一个字符
jmp L1
;完成之后,如果前面有删除的字符,要把后面的字符整体前移
quitLoop:
cmp ebx, 0
je quit ;等于0,说明没有匹配上字符,退出
mov ecx, ebx
;整体左移,列如:###hello===>hellollo
L2:
mov al,[edi+ebx]
mov [esi], al
cmp al,0
je L3 ;说明已经到字符串尾,这个时候的字符串是hellollo
inc esi
inc edi
jmp L2
;清除后面的字符hellollo===>hello
L3:
mov BYTE PTR[edi+ebx], 0
dec ebx
loop L3
quit:
ret
StrTrim ENDP
main PROC
mov esi, OFFSET string_1
INVOKE StrTrim, ADDR string_1, '#'
INVOKE ShowString, ADDR string_1
INVOKE StrTrim, ADDR string_2, '#'
INVOKE ShowString, ADDR string_2
INVOKE StrTrim, ADDR string_3, '#'
INVOKE ShowString, ADDR string_3
INVOKE StrTrim, ADDR string_4, '#'
INVOKE ShowString, ADDR string_4
INVOKE StrTrim, ADDR string_5, '#'
INVOKE ShowString, ADDR string_5
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
***14.去除一组字符
编写Str_trim 过程的变体,使得主调程序能从字符串末尾删除一组字符。比如,若调用过程时,有一指针指向字符串“ABC#S&”,且向过程传递了过滤字符数组“%#!;S&*”的指针,则结果字符串应为“ABC”。
;9.10_14.asm 9.10 编程练习 ***14.去除一组字符
;编写Str_trim 过程的变体,使得主调程序能从字符串末尾删除一组字符。
;比如,若调用过程时,有一指针指向字符串“ABC#S&”,
;且向过程传递了过滤字符数组“%#!;S&*”的指针,则结果字符串应为“ABC”。
INCLUDE Irvine32.inc
.data
string_1 BYTE "Hello@%$#",0
string_2 BYTE "Hello@World@@@",0
string_3 BYTE "Hello",0
string_4 BYTE "H@$#&@@@",0
string_5 BYTE "@Hello%&",0
mathchString BYTE '$%&#@', 0
.code
;------------------------------------------
;获取字符串的长度。
;返回值,EAX返回字符串的长度
;------------------------------------------
StrLengthA PROC USES edi,
pString:PTR BYTE
mov edi, pString ;指向字符串
mov eax, 0 ;字符计数器
L1: cmp BYTE PTR[edi], 0 ;字符结束?
je L2 ;是:退出
inc edi ;否:指向下一个字符
inc eax ;计数器加1
jmp L1
L2: ret
StrLengthA ENDP
;------------------------------------------
;显示字符串。
;返回:无
;------------------------------------------
ShowString PROC pString1:PTR BYTE
mov edx, pString1
call WriteString
call Crlf
ret
ShowString ENDP
;------------------------------------------
;匹配分隔符
;返回:匹配上了eax=0,否则为任意值
;------------------------------------------
MatchDelimiter PROC USES ebx esi edi,
pString1:PTR BYTE,
char:BYTE
mov esi, pString1
L1:
mov bl, [esi]
cmp bl, 0 ;等于0表示到字符串末尾了
je quit
cmp bl, char
jne L2 ;不相等跳转到L2,比较下一个字符
mov eax, 0 ;相待,把eax赋为0,退出循环
jmp quit
L2:
inc esi
jmp L1
quit:
ret
MatchDelimiter ENDP
;------------------------------------------
;从字符串末尾删除所有与给定分隔符匹配的字符。
;返回:无
;------------------------------------------
StrTrim PROC USES eax ecx edi,
pStr:PTR BYTE, ;指向字符串
pMat:PTR BYTE ;匹配要删除的字符
mov edi, pStr ;准备调用Str_length
INVOKE StrLengthA, edi ;用EAX返回长度
cmp eax, 0 ;长度是否为零?
je L3 ;是:立刻退出
mov ecx, eax ;否:ECX=字符串长度
dec eax
add edi, eax ;指向最后一个字符
L1: mov al, [edi] ;取一个字符
;cmp al, char ;是否为分隔符?
INVOKE MatchDelimiter, pMat, al
cmp al, 0 ;匹配成功eax = 0
jne L2 ;否:插入空字节
dec edi ;是:继续后退一个字符
loop L1 ;直到字符串的第一个字符
L2: mov BYTE PTR [edi+1], 0 ;插入一个空字节
L3: ret
StrTrim ENDP
main PROC
INVOKE ShowString, ADDR string_1
INVOKE StrTrim, ADDR string_1, ADDR mathchString
INVOKE ShowString, ADDR string_1
INVOKE ShowString, ADDR string_2
INVOKE StrTrim, ADDR string_2, ADDR mathchString
INVOKE ShowString, ADDR string_2
INVOKE ShowString, ADDR string_3
INVOKE StrTrim, ADDR string_3, ADDR mathchString
INVOKE ShowString, ADDR string_3
INVOKE ShowString, ADDR string_4
INVOKE StrTrim, ADDR string_4, ADDR mathchString
INVOKE ShowString, ADDR string_4
INVOKE ShowString, ADDR string_5
INVOKE StrTrim, ADDR string_5, ADDR mathchString
INVOKE ShowString, ADDR string_5
INVOKE ExitProcess, 0
main ENDP
END main
运行调试: