《汇编语言:基于X86处理器》第5章 复习题和练习,编程练习
本篇记录《汇编语言:基于X86处理器》第5章的复习题和练习,编程练习的学习。
5.8 复习题和练习
5.8.1 简答题
1.哪条指令将全部的32位通用寄存器压人堆栈?
答:pushad
2.哪条指令将32 位 EFLAGS 寄存器压人堆栈?
答:pushfd
3.哪条指令将堆栈内容弹出到EFLAGS寄存器?
答:popfd
4.挑战:另一种汇编器(称为NASM)允许PUSH 指令列出多个指定寄存器。为什么这种方法可能会比 MASM 中的 PUSHAD 指令要好?下面是一个NASM 示例:
PUSH EAX EBX ECX
答:NASM 的方法有以下几个优势:
1. 更精细的控制
- NASM 方式:可以选择性地压入需要的寄存器,避免不必要的压栈操作
- PUSHAD:总是压入所有 8 个通用寄存器(EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI),即使你只需要其中几个
2. 更好的性能
- 压入更少的寄存器意味着:
-
- 更少的内存写入操作
- 更少的堆栈空间占用
- 更快的执行速度
3. 更清晰的代码意图
- 明确列出要保存的寄存器使代码更易读
- 读者可以立即知道哪些寄存器需要被保护
4. 更灵活的寄存器顺序
- 可以控制寄存器压栈的顺序,这在某些特定场景下可能有用
- PUSHAD 的压栈顺序是固定的
示例对比
nasm
; NASM 方式 - 只压入需要的寄存器
PUSH EAX EBX ECX ; 只压入3个寄存器
...
POP ECX EBX EAX ; 对称地弹出; MASM PUSHAD 方式
PUSHAD ; 压入所有8个寄存器,即使只需要3个
...
POPAD ; 弹出所有
对于只需要保存部分寄存器的情况,NASM 的方式显然更高效、更灵活。
5.挑战:假设没有 PUSH 指令,另外编写两条指令来完成与push eax 同样的操作。
答:执行push指令时,首先会把栈指错减4,然后再把数据压栈,因此可以用以下两条指令来完成。
SUB ESP, 4 ; 将栈指针 ESP 减 4,为新值腾出空间
MOV [ESP], EAX ; 将 EAX 的值存储到新的栈顶 [ESP]
6.(真/假):RET指令将栈顶内容弹出到指令指针寄存器。
答:真
7.(真/假):Microsoft汇编器不允许过程嵌套调用,除非在过程定义中使用了NESTED运算符。
答:假
8.(真/假):在保护模式下,每个过程调用最少使用 4个字节的堆栈空间。
答:假。在保护模式下,每个过程调用最少使用 8 个字节 的堆栈空间(32 位模式下),而不是 4 个字节。这是因为:
- 返回地址占用 4 字节(32 位模式下,EIP 寄存器是 32 位的)。
- 调用时可能隐式压入参数或保存寄存器,但即使是最简单的
CALL
指令,也会至少压入返回地址(4 字节)。 - 在 16 位实模式下,返回地址是 2 字节(CS:IP),但在保护模式下是 4 字节(32 位 EIP)。
9.(真/假):向过程传递32 位参数时,不能使用ESI 和 EDI 寄存器。
答: 假。在x86汇编语言中,向过程传递32位参数时,可以使用ESI和EDI寄存器。虽然一些调用约定(如stdcall
、cdecl
、fastcall
等)可能规定了特定的寄存器或栈用于参数传递,但并没有硬性规则禁止使用ESI和EDI寄存器来传递参数。
10.(真/假):ArraySum 过程(5.2.5节)接收一个指向任何一个双字数组的指针。
答:假。ArraySum过程接收两个参数:一个指向32位整数数组的指针,以及一个数组元素个数的计数器。
11.(真/假):USES运算符能让程序员列出所有在过程中会被修改的寄存器。
答:真。 在 x86 汇编语言(特别是使用 MASM 或类似汇编器时),USES
运算符(或伪指令)允许程序员列出所有在过程中会被修改的寄存器。汇编器会自动在过程的入口处保存这些寄存器的值(压栈),并在过程返回前恢复它们(弹栈),从而简化寄存器的保护工作。
12.(真/假);USES 运算符只能产生 PUSH 指令,因此程序员必须自己编写代码完成 POP 指令功能。
答:假,ret指令之前会自动pop寄存器。
13.(真/假):用USES伪指令列出寄存器时,必须用逗号分隔寄存器名。
答:假。是用空格分隔寄存器名。
14.修改ArraySum 过程(5.2.5节)中的哪条(些)语句,使之能计算16位字数组的累积和?编写这个版本的ArraySum并进行测试。
答:把定义的类型由DWORD改为WORD,添加一个中间寄存器edx,用来存放各数组元素,同时初始化eax,edx为0。
;5.8.1_14.asm 5.8.1 简答题
;14.修改ArraySum 过程(5.2.5节)中的哪条(些)语句,使之能计算16位字数组的累积和?编写这个版本的ArraySum并进行测试。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
array WORD 1000h,2000h,3000h,4000h,5000h
theSum DWORD ?.code
main PROCmov esi, OFFSET array ;ESI指向数组mov ecx, LENGTHOF array ;ECX=数组计数器mov eax, 0 ;添加, 清空eax的值,防止高16位的影响mov edx, 0 ;添加, 清空edx的值,防止高16位的影响call ArraySum16 ;计算和数mov theSum, eax ;用EAX返回和数INVOKE ExitProcess,0
main ENDP
;--------------------------------------------------------
;ArraySum16.asm
;计算16位整数数组元素之和。
;接收:ESI=数组偏移量
; ECX=数组元素的个数
;返回:EAX=数组元素之和
;--------------------------------------------------------
ArraySum16 PROCpush esi ;保存ESIpush ecx ;保存ECXmov eax,0 ;设置和数为0
L1: movzx edx, WORD PTR [esi] ;添加一个中间量,接收16位数据add eax, edx ;修改,通过中间寄存器相加add esi,TYPE WORD ;指向下一个整数loop L1 ;按照数组大小重复pop ecx ;恢复ECX和ESIpop esi ret ;和数在EAX中
ArraySum16 ENDP
END main
运行调试:
15.执行下列指令后,EAX 的最后数值是多少?
push 5
push 6
pop eax
pop eax
答:EAX的最后数值是5,首先弹出的是6,然后再弹出5把之前EAX里的内容覆盖。
;5.8.1_15.asm .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCpush 5push 6pop eaxpop eaxINVOKE ExitProcess,0
main endp
end main
运行调试:
16.运行如下示例代码时,下面哪个对执行情况的陈述是正确的?
main PROCpush 10push 20call Ex2Subpop eaxINVOKE ExitProcess,0
main endpEx2Sub PROCpop eaxret
Ex2Sub ENDP
a.到第6行代码,EAX将等于 10
b.到第 10 行代码,程序将因运行时错误而暂停
c.到第6行代码,EAX将等于20
d.到第 11行代码,程序将因运行时错误而暂停
答:a..错。这里call Ex2Sub指令时,会把第6行的偏移值压栈,这里栈中就有3个数据了。
b.错。到第10行代码时,会把栈中的偏移值弹出给eax。
c.错。原理同a。
d.正确。执行到第11行时,因为第10行已经把偏值出栈给eax时,ret执行时转到的偏移值是20的位置。这个不是原来的指令偏移值,会产生错误而暂停。
;5.8.1_16.asm 5.8.1 简答题
;16.运行如下示例代码时,下面哪个对执行情况的陈述是正确的?.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCpush 10push 20call Ex2Subpop eaxINVOKE ExitProcess,0
main endpEx2Sub PROCpop eaxret
Ex2Sub ENDP
end main
运行调试:
17.运行如下示例代码时,下面哪个对执行情况的陈述是正确的?
main PROCmov eax, 30push eaxpush 40call Ex3SubINVOKE ExitProcess,0
main endpEx3Sub PROCpushamov eax,80poparet
Ex3Sub ENDP
a.到第6 行代码,EAX 将等于 40
b.到第 6 行代码,程序将因运行时错误而暂停
c.到第6行代码,EAX将等于30
d.到第 13 行代码,程序将因运行时错误而暂停
答:a.错。到第6行代码,EAX将等于30。
b.错。程序正常执行。
c.正确。
d.错。程序正常执行。
;5.8.1_17.asm .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCmov eax, 30push eaxpush 40call Ex3SubINVOKE ExitProcess,0
main endpEx3Sub PROCpushamov eax,80poparet
Ex3Sub ENDP
end main
运行调试:
18.运行如下示例代码时,下面哪个对执行情况的陈述是正确的?
main PROCmov eax, 40push offset Herejmp Ex4Sub
Here:mov eax, 30INVOKE ExitProcess,0
main endpEx4Sub PROCret
Ex4Sub ENDP
a.到第 7 行代码,EAX 将等于30
b.到第 4 行代码,程序将因运行时错误而暂停
c.到第6行代码,EAX 将等于30
d.到第 11 行代码,程序将因运行时错误而暂停
答:a.正确。
b.错误,程序正常执行,push指令把jmp指令的下一条指令偏移值入栈,ret指令执行时,从栈中取值赋给EIP寄存器,继续执行Here标号的指令。
c.错误,到第6行时eax的值为40,执行完第6行eax的值才为30。
d.错误,程序正常执行。
;5.8.1_18.asm .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCmov eax, 40push offset Herejmp Ex4Sub
Here:mov eax, 30INVOKE ExitProcess,0
main endpEx4Sub PROCret
Ex4Sub ENDP
end main
运行调试:
eax最后结果:
19.运行如下示例代码时,下面哪个对执行情况的陈述是正确的?
main PROCmov edx, 0mov eax, 40push eax call Ex5Sub INVOKE ExitProcess,0
main endpEx5Sub PROCpop eax pop edx push eax ret
Ex5Sub ENDP
a.到第 6 行代码,EAX 将等于 40
b.到第 13 行代码,程序将因运行时错误而暂停
c.到第6行代码,EAX 将等于 0
d.到第 11 行代码,程序将因运行时错误而暂停
答:a.错误。EAX的值为偏移值
b.错误,程序正常
c.错误。EAX的值为偏移值
d.错误,程序正常
完整的测试代码:
;5.8.1_19.asm .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCmov edx, 0mov eax, 40push eax ;eax入栈call Ex5Sub ;call指令的下一条指令偏移值入栈INVOKE ExitProcess,0
main endpEx5Sub PROCpop eax ;弹出偏移值赋给eaxpop edx ;弹出eax的值给edx, 即edx=40push eax ;把eax的值入栈,相当于把偏移值再次入栈ret ;回到偏移值处的指令执行
Ex5Sub ENDP
end main
运行调试:
20.执行下述代码时,哪些数值将被写人数组?
;5.8.1_20.asm .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
array DWORD 4 DUP (0).code
main PROCmov eax, 10mov esi, 0call proc_1add esi, 4add eax, 10mov array[esi], eaxINVOKE ExitProcess,0
main endpproc_1 PROCcall proc_2add esi, 4add eax, 10mov array[esi], eaxret
proc_1 ENDP
proc_2 PROCcall proc_2add esi, 4add eax, 10mov array[esi], eaxret
proc_2 ENDP
proc_3 PROCmov array[esi], eaxret
proc_3 ENDP
end main
答:写入数组的值为10,20,30,40.
调试运行:
5.8.2 算法基础
下列习题可以用32 位或 64 位代码解答。
1.编写一组语句,仅用PUSH和POP指令来交换EAX和EBX寄存器(或64位的RAX和RBX)中的值。
; 5.8.2_1.asm 5.8.2 算法基础
;1.编写一组语句,仅用PUSH和POP指令来交换EAX和EBX寄存器(或64位的RAX和RBX)中的值。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCmov eax, 1111hmov ebx, 2222h;交换push eaxpush ebxpop eaxpop ebxINVOKE ExitProcess, 0
main ENDP
END main
运行调试:
2.假设需要一个子程序的返回地址在内存中比当前堆栈中的返回地址高3个字节。编写一组指令放在该子程序RET指令之前,以完成这个任务。
; 5.8.2_2.asm 5.8.2 算法基础
;2.假设需要一个子程序的返回地址在内存中比当前堆栈中的返回地址高3个字节。
;编写一组指令放在该子程序RET指令之前,以完成这个任务。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCcall SPAdd3Byte;方法2;add DWORD PTR [esp], 3 ;默认是ss:[esp];偏移3个字节,确保程序正常结束nop nopnopINVOKE ExitProcess, 0
main endpSPAdd3Byte PROC;方法1pop eax ; 将当前返回地址弹出到EAX寄存器中add eax, 3 ; 将返回地址增加3个字节push eax ; 将修改后的返回地址压回堆栈; 然后执行正常的RET指令ret
SPAdd3Byte endp
end main
运行调试:
3.高级语言的函数通常在堆栈中的返回地址下,立刻声明局部变量。在汇编语言子程序开端编写一条指令来保留两个双字变量的空间。然后,对这两个局部变量分别赋值 1000h 和 2000h。
; 5.8.2_3.asm 5.8.2 算法基础
;3.高级语言的函数通常在堆栈中的返回地址下,立刻声明局部变量。
;在汇编语言子程序开端编写一条指令来保留两个双字变量的空间。
;然后,对这两个局部变量分别赋值 1000h 和 2000h。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCcall localVariableINVOKE ExitProcess, 0
main ENDP;局量变量子程序
localVariable PROCLOCAL var1:DWORD,var2:DWORDmov var1, 1000h ; 将第1个局部变量赋值为1000hmov var2, 2000h ; 将第2个局部变量赋值为2000hret
localVariable ENDP;方法2
localVariable2 PROCpush ebp ; 保存基址指针mov ebp, esp ; 设置新的基址指针sub esp, 8 ; 为两个双字局部变量分配8字节堆栈空间; 将第一个局部变量赋值为1000hmov dword ptr [ebp - 4], 1000h; 将第二个局部变量赋值为2000hmov dword ptr [ebp - 8], 2000hmov esp, ebp ; 恢复栈指针pop ebp ; 恢复基址指针ret ; 返回调用者
localVariable2 ENDPEND main
运行效果:
从以上可以看出,局部变量保存在栈中,方法1和方法2是一样的效果。
4.编写一组语句,用变址寻址方式将双字数组中的元素复制到同一数组中其前面的一个位置上。
; 5.8.2_4.asm 5.8.2 算法基础
;4.编写一组语句,用变址寻址方式将双字数组中的元素复制到同一数组中其前面的一个位置上。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
arrayD DWORD 1111h, 2222h, 3333h, 4444h.code
main PROCmov esi, OFFSET arrayDmov ecx, LENGTHOF arrayDdec ecx
L1: mov eax, [esi]xchg eax, [esi+4]mov [esi], eaxadd esi, 4loop L1INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
复制后的数据:
5. 编写一组语句显示子程序的返回地址。注意,不论如何修改堆栈,都不能阻止子程序返回到调用程序。
; 5.8.2_5.asm 5.8.2 算法基础
; 5. 编写一组语句显示子程序的返回地址。注意,不论如何修改堆栈,都不能阻止子程序返回到调用程序。;.386
;.model flat,stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORDINCLUDE Irvine32.inc.code
main PROCcall displayReturnAddress ;执行call指令时,会把nop所在指令的偏移值压栈nopINVOKE ExitProcess, 0
main ENDPdisplayReturnAddress PROC;保存所有受影响的寄存器push ebppush eaxpush edx;获取返回地址mov ebp, espadd ebp, 12 ;跳过保存的ebp(4)+eax(4)+edx(4) = 12字节mov eax, [ebp] ;返回地址位于ebp处,即调用此函数时的下一条指令的偏移值。;显示返回地址call Crlf ;换行mov edx, OFFSET msgReturnAddresscall WriteStringcall WriteHex ;显示EAX中的十六进制值(返回地址)call Crlf;恢复寄存器pop edxpop eaxpop ebp;确保存正常返回ret
msgReturnAddress BYTE "Return address:", 0
displayReturnAddress ENDP
END main
运行调试:
返回址址:00403665
5.9 编程练习
为解答编程练习编写程序时,尽量使用多个过程。除非读者的指导者另有规定,否则遵循本书使用的风格和命名规则。在每个过程的开始和非常规语句处使用解释性注释。
*1.设置文本颜色
用循环结构,编写程序用四种颜色显示同一个字符串。调用本书链接库的 SetTextColor 过程。可以选择任何颜色,但你会发现改变前景色是最简单的。
; 5.9_1.asm INCLUDE Irvine32.inc.data
arrayB BYTE "display different color strings", 0 ;显示的文本
arrayColor WORD red, gray, green, yellow ;文字颜色值.code
main PROCmov esi, 0mov ecx, 4 ;循环4次,产生4种颜色;mov eax, red + (blue * 16) ;文字颜色+背景色
L1:movzx eax, WORD PTR arrayColor[esi] ;设置颜色值call SetTextColor ;设置文字颜色mov edx, OFFSET arrayBcall WriteString ;显示文本add esi, 2 call Crlf ;换行loop L1;还原默认文本颜色mov eax, white ;设置颜色值call SetTextColor ;设置文字颜色INVOKE ExitProcess,0
main ENDP
END main
运行结果:
**2.链接数组项
假设给定的3个数据项分别代表一个表、一个字符数组以及一个链接索引数组的起始变址。编写程序遍历链接,并按正确顺序定位字符。将被定位的每个字符都复制到一个新数组中。假设使用如下示例数据,且各数组都从 0开始变址:
start 1
chars: H, A, C, E, B, D, F, G
links: 0, 4, 5, 6, 2, 3, 7, 0
复制到输出数组的数值(依次)为A、B、C、D、EF、G、H。字符数组声明为BYTE 类型,为了使问题更加有趣,将链表数组声明为DWORD类型。
; 5.9_1.asm 编程练习 **2.链接数组项INCLUDE Irvine32.inc.data
start DWORD 1 ; 定义起始索引
chars BYTE 'H', 'A', 'C', 'E', 'B', 'D', 'F', 'G' ; 定义字符数组 (BYTE类型)
links DWORD 0, 4, 5, 6, 2, 3, 7, 0 ; 定义链接索引数组 (DWORD类型)
output BYTE 8 DUP(?) ,0 ; 输出数组 (大小与字符数组相同)
outputIndex DWORD 0 ; 用于跟踪输出数组位置的计数器.code
main PROCmov esi, start ;初始化ESI为当前索引 (start值) esi = 1mov edi, OFFSET output ;查看内存数据测试用linksLoop: ;循环遍历链表cmp esi, 0 ;检查是否到达链表末尾 (links[esi] == 0)je endLoop ;结束,跳出循环mov al, chars[esi] ;获取当前字符 AL = chars[esi];将字符存入输出数组mov edi, outputIndex ;EDI = outputIndexmov output[edi], al ;output[outputIndex] = ALinc outputIndex ;增加outputIndex;移动到下一个链接mov esi, links[esi*4] ;ESI = links[ESI] (乘以4因为links是DWORD);继续循环jmp linksLoopendLoop:;显示复制后的数据mov edx, OFFSET outputcall WriteStringcall CrlfINVOKE ExitProcess, 0
main ENDP
END main
运行调试:
*3.简单加法(1)
编写程序:清屏,将鼠标定位到屏幕中心附近,提示用户输入两个整数,两数相加,并显示和数。
; 5.9_3.asm 编程练习 *3.简单加法(1)
;编写程序:清屏,将鼠标定位到屏幕中心附近,提示用户输入两个整数,两数相加,并显示和数。INCLUDE Irvine32.inc.data
rows BYTE ?
cols BYTE ?
intVar1 DWORD ?
intVar2 DWORD ?
msg1 BYTE "please input var1:",0
msg2 BYTE "please input var2:",0
msg3 BYTE "result = ",0.code
main PROCcall Clrscr ;清屏Call GetMaxXY ;获取屏幕大小,al存放行数, dl存放列数mov rows, almov cols, dlsub rows, 20sub cols, 110mov dh, rows ;把行数存放到dh中,列数存放在dl中mov dl, cols Call Gotoxy ;定义到中心mov edx, OFFSET msg1call WriteString ;显示提示输入文字call ReadInt ;从键盘读入32位整数mov intVar1, eaxadd rows,1mov dh, rows mov dl, cols Call Gotoxy ;定义到偏移位置;call Crlf ;换行mov edx, OFFSET msg2call WriteString ;显示提示输入文字call ReadInt ;从键盘读入32位整数mov intVar2, eax;求和add eax, intVar1add rows,1mov dh, rows mov dl, cols Call Gotoxy ;定义到偏移位置;call Crlf ;换行mov edx, OFFSET msg3call WriteString ;显示结果文本call WriteDec ;显示和add rows,1mov dh, rows mov dl, cols Call Gotoxy ;定义到偏移位置call Crlf ;换行;call WaitMsg ; "Press any key...";call ClrscrINVOKE ExitProcess,0
main ENDP
END main
运行调试
**4.简单加法(2)
以前一题编写的程序为起点,在新程序中,用循环结构将上述同样的步骤重复3次。每次循环迭代后清屏。
; 5.9_4.asm 编程练习 **4.简单加法(2)
;以前一题编写的程序为起点,在新程序中,用循环结构将上述同样的步骤重复3次。每次循环迭代后清屏。INCLUDE Irvine32.inc.data
rows BYTE ?
cols BYTE ?
intVar1 DWORD ?
intVar2 DWORD ?
msg1 BYTE "please input var1:",0
msg2 BYTE "please input var2:",0
msg3 BYTE "result = ",0.code
main PROCcall Clrscr ;清屏Call GetMaxXY ;获取屏幕大小,al存放行数, dl存放列数mov rows, almov cols, dlsub rows, 25sub cols, 110mov dh, rows ;把行数存放到dh中,列数存放在dl中mov dl, cols Call Gotoxy ;定义到中心mov ecx, 3
L1:call Clrscrmov dh, rows mov dl, cols Call Gotoxy ;定义到偏移位置mov edx, OFFSET msg1call WriteString ;显示提示输入文字call ReadInt ;从键盘读入32位整数mov intVar1, eaxadd rows,1mov dh, rows mov dl, cols Call Gotoxy ;定义到偏移位置mov edx, OFFSET msg2call WriteString ;显示提示输入文字call ReadInt ;从键盘读入32位整数mov intVar2, eax;求和add eax, intVar1add rows,1mov dh, rows mov dl, cols Call Gotoxy ;定义到偏移位置mov edx, OFFSET msg3call WriteString ;显示结果文本call WriteDec ;显示和call Crlf ;换行call WaitMsg ; "Press any key..."dec ecxcmp ecx, 0je endL1jmp L1;loop L1endL1:call Crlf ;换行;call WaitMsg ; "Press any key...";call ClrscrINVOKE ExitProcess,0
main ENDP
END main
运行调试:
*5.BetterRandomRange过程
Irvine32 链接库的RandomRange过程在0~N-1范围内生成一个伪随机整数。本题任务是编写该过程的改进版,在M~N-1围内生成一个整数。调用程序用EBX传递M,用EAX传递 N。若将该过程称为BetterRandomRange,则下述代码为测试示例:
mov ebx, -300 ;下限
mov eax, 100 ;上限
call BetterRandomRange
编写一个简短的测试程序,调用 BetterRandomRange,并循环50次。显示每次随机生成的数值。
; 5.9_5.asm 编程练习 *5.BetterRandomRange过程INCLUDE Irvine32.inc.data
randVal DWORD ?.code
main PROCmov ebx, -300 ;下限mov eax, 100 ;上限call BetterRandomRange ;生成满园在-300~100之间mov randVal, eaxcall WriteInt ;输出到屏幕INVOKE ExitProcess,0
main ENDPBetterRandomRange PROCpush edxpush ecx;计算范围大小(M~N-1)mov ecx, eax ;ecx = Nsub ecx, ebx ;ecx = N - M; 调用 RandomRange 生成 0 到 (N-M-1) 的随机数call RandomRangeadd eax, ebx ;eax = 随机数+Mpop ecx ;恢复寄存器pop edxret
BetterRandomRange ENDP
END main
运行调试:
**6.随机字符串
创建过程,生成长度为L的随机字符串,字符全为大写字母。调用过程时,用EAX传递长度L的值,并传递一个指针指向用于保存该随机字符串的字节数组。编写测试程序调用该过程20次并在控制台窗口显示字符串。
; 5.9_6.asm 编程练习 **6.随机字符串
;创建过程,生成长度为L的随机字符串,字符全为大写字母。
;调用过程时,用EAX传递长度L的值,并传递一个指针指向用于保存该随机字符串的字节数组。
;编写测试程序调用该过程20次并在控制台窗口显示字符串。INCLUDE Irvine32.inc.data
arrayB BYTE 26 DUP(?).code
main PROCmov ecx, 20 ;调用过程20次
L2:call UppercaseStringmov edx, OFFSET arrayBcall Crlf ;换行call WriteString ;显示字符串loop L2INVOKE ExitProcess,0
main ENDP
;生成大写字符串
UppercaseString PROCpush ecx ;保存外层循环次数mov ecx, LENGTHOF arrayB ;字符串长度mov edi, OFFSET arrayBmov esi, 0
L1:mov eax, 26 ;随机数生成范围0~26call RandomRange ;生成随机数 0~26add AL, 'A' ;转成大写字母A~Zmov BYTE PTR arrayB[esi], AL ;存入字符串中inc esiloop L1pop ecx ;弹出外层循环的ecxret
UppercaseString ENDPEND main
运行结果:
*7.随机屏幕位置
编写程序在100个随机屏幕位置显示一个字符,计时延迟为100毫秒。提示:使用GetMaxXY过程确定控制台窗口当前大小。
; 5.9_7.asm 编程练习 *7.随机屏幕位置INCLUDE Irvine32.inc.data
char BYTE 'A' ; 要显示的字符
delayTime DWORD 100 ; 延迟时间(毫秒).code
main PROCcall Randomize ; 初始化随机数种子mov ecx, 100 ; 循环100次
L1:push ecx ; 保存循环计数器; 获取控制台窗口大小call GetMaxXY ; DX = 行数, AX = 列数movzx ebx, ax ; 最大列数movzx edx, dx ; 最大行数; 生成随机列位置 (0到ebx-1)mov eax, ebx;这里eax不能等0,否则会出错cmp eax, 0jne RCmov eax, 1 ;修复eax为0的问题
RC:call RandomRangemov dl, al ; DL = 列位置; 生成随机行位置 (0到edx-1)mov eax, edx;这里eax不能等0,否则会出错cmp eax, 0jne RLmov eax, 1 ;修复eax为0的问题
RL:call RandomRangemov dh, al ;DH = 行位置; 设置光标位置movzx edx, dx ;清除高16位call Gotoxy; 显示字符mov al, charcall WriteChar; 延迟100毫秒mov eax, delayTimecall Delaypop ecx ; 恢复循环计数器loop L1; 程序结束前将光标移到最后一行call GetMaxXY ; 获取最大行数dec dx ; 行数-1mov dh, dl ; 设置行mov dl, 0 ; 设置列0call GotoxyINVOKE ExitProcess,0
main ENDPEND main
运行调试:
**8.颜色矩阵
编写程序在所有可能的前景色和背景色组合(16x16=256)中显示一个字符。颜色编号从0到15,因此可以用循环嵌套产生所有可能的组合。
; 5.9_8.asm 编程练习 **8.颜色矩阵INCLUDE Irvine32.inc.data
char BYTE 'A'
color DWORD ?.code
main PROCmov ecx, 16
L1:push ecxmov color, ecx ;前景色0~15dec colormov ecx, 16
L2: ;color = color + (ecx - 1) * 16mov eax, ecxdec eaxshl eax, 4add eax, colorcall SetTextColormov al, charcall WriteChar ;显示字符loop L2pop ecxloop L1INVOKE ExitProcess,0
main ENDP
END main
运行结果:
***9 递归过程
当一个过程调用其自身时,就称之为直接递归。当然,编程者不会希望一个过程一直调用其自身,因为运行时堆栈会占满。相反,必须用某些方法来限制递归。编写程序调用一个递归过程。在过程中,用计数器加1的方式确定其执行的次数。用调试器执行编写的程序,在程序结束时,查看计数器的值。向 ECX 输入一个值来指定编程者允许的连续递归次数。只能使用 LOOP 指令(不能使用后续章节的其他条件判断语句),找出方法使递归过程按给定次数调用其自身。
; 5.9_9.asm 编程练习 ***9 递归过程
;当一个过程调用其自身时,就称之为直接递归。当然,编程者不会希望一个过程一直调用其自身,因为运行时堆栈会占满。
;相反,必须用某些方法来限制递归。编写程序调用一个递归过程。在过程中,用计数器加1的方式确定其执行的次数。
;用调试器执行编写的程序,在程序结束时,查看计数器的值。向 ECX 输入一个值来指定编程者允许的连续递归次数。
;只能使用 LOOP 指令(不能使用后续章节的其他条件判断语句),找出方法使递归过程按给定次数调用其自身。.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD.data
counter DWORD 0 ;计数器,记录递归调用次数
maxRecursion DWORD ? ;最大递归次数,从ECX传入.code
main PROC mov ecx, 9 ;设置最大递归次数为9mov maxRecursion, ecx ;保存最大递归次数call RecursiveProc ;调用递归过程;程序结束时,counter的值就是递归调用的次数INVOKE ExitProcess,0
main ENDPRecursiveProc PROC; 增加计数器inc counter;保存当前ecx值push ecx;设置循环次数为maxRecursion - countermov ecx, maxRecursionsub ecx,counter;如果ecx > 0,则继续递归jecxz NoRecurse ;果ecx=0则跳过递归call RecursiveProc ;递归调用
NoRecurse:;恢复ecx值pop ecxret
RecursiveProc ENDP
END main
运行结果:
***10.斐波那契生成器
编程一个过程,生成含有N个数值的斐波那契(Fibonacci)数列,并将它们保存到一个双字数组中。需要输人的参数为:双字数组指针和生成数值个数的计数器。编写一个测试程序来调用该过程,使N-47。数组中的第一个值为1,最后一个值为2971 215 073。使用Visual Studio 调试器打开并查看数组内容。
; 5.9_10.asm 编程练习 ***10.斐波那契生成器.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
arrayD DWORD 48 DUP(?).code
main PROCmov ecx, 47 ;数值个数mov esi, 0 ;保存数组的下标mov edi, OFFSET arrayD ;查看内存数据用push eax ;保存寄存器push ebxpush edxcall Fibonacci ;生成Fibonacci数列pop edx ;还原寄存器的值pop ebxpop eax INVOKE ExitProcess,0
main ENDPFibonacci PROCmov eax, 0mov ebx, 0mov edx, 0 ;edx存放结果
L1:mov edx, eaxadd edx, ebx ;edx=eax+ebxcmp edx, 0jne assignmov edx, 1
assign:mov eax, ebx ;a = bmov ebx, edx ;b = cmov DWORD PTR arrayD[esi], edx ;把数存放到数组中add esi, 4 ;下一个数组位置loop L1ret
Fibonacci ENDPEND main;C语言算法
;void fibonacci(int n) {
; int a = 0, b = 1, c;
; printf("斐波那契数列前%d项:\n", n);
; for (int i = 0; i < n; i++) {
; printf("%d ", a);
; c = a + b;
; a = b;
; b = c;
; }
;}
运行调试:
***11.找出 K的倍数
现有一字节数组,大小为 N,编写过程,找出所有小于N的K的倍数。在程序开始时,将该数组中所有元素都初始化为零,然后,每计算出一个倍数,就将数组中相应位置1。过程对其要修改的所有寄存器都要进行保存和恢复。当 K=2 和 K=3 时分别调用该过程。令 N-50,在调试器中运行程序并验证数组数值是否正确。
注意: 该过程在寻找素数时是一个很有用的工具。寻找素数的一个有效算法被称为厄拉多塞过滤法(Sieve of Eratosthenes)。在第6章学习条件判断语句之后,读者就能够实现这个算法了。
;5.9_11.asm 编程练习 ***11.找出 K的倍数
;现有一字节数组,大小为 N,编写过程,找出所有小于N的K的倍数。
;在程序开始时,将该数组中所有元素都初始化为零,然后,每计算出一个倍数,就将数组中相应位置1。
;过程对其要修改的所有寄存器都要进行保存和恢复。当 K=2 和 K=3 时分别调用该过程。
;令 N-50,在调试器中运行程序并验证数组数值是否正确。.386
.model flat, stdcall
.stack 4096ExitProcess PROTO, dwExitCode:DWORD.data
N = 50
array BYTE N DUP(?) ;定义大小为50的字节数组
K DWORD ? ;用于存储当前的K值.code
main PROCmov esi, OFFSET array;第一次调用:K=2mov K, 2call FindMultiples;第二次调用:K=3mov K, 3call FindMultiples;退出程序INVOKE ExitProcess, 0
main ENDP; 找出K的倍数并将数组中相应位置1的过程
FindMultiples PROC;保存寄存器push eaxpush ebxpush ecxpush edxpush esi;初始化数组为全0mov ecx, Nmov esi, 0
init_loop:mov array[esi], 0inc esiloop init_loop; 找出所有小于N的K的倍数mov eax, K ; eax = Kmov ebx, 0 ; ebx用于计数find_loop:add ebx, eax ; ebx += K (下一个倍数)cmp ebx, Njge done ; 如果ebx >= N,结束;将数组中相应位置1mov array[ebx], 1jmp find_loopdone:; 恢复寄存器pop esipop edxpop ecxpop ebxpop eaxret
FindMultiples ENDPEND main
运行调试:
查看K=2的标记
K= 3
K=3标记的数组