最近有点自闭,事情贼多,还要学这么变态的汇编(╥╯^╰╥)。经过异常艰难的探索,终于完成了课程的第一个作业——用masm32寻找1-100的质数,写下此篇博客,转换一下心情。( • ̀ω•́ )✧
事先声明,我是汇编语言的萌新,接下来的代码可能存在许多多余和不合适的地方,而且我把所有的变量都存放在寄存器里面,这是极不合理的,在接下来的两次作业(斐波那契数列和八皇后问题)中我会加深对汇编的理解,改正代码中由于技术不足而出现的问题。
一. C语言参考程序
#include "stdio.h"
int main()
{
for(int i=2;i<100;i++){
bool flag = true;
for(int j=2;j<i;j++){
if(i%j==0){
flag = false;
break;
}
}
if(flag){
printf("%d ",i);
}
}
}
上述代码有几个地方需要强调:
(1)使用flag作为标志记录是否是一个质数。
(2)在判断是否除尽的时候我直接使用的条件是j<i而不是j<i/2是为了方便写汇编。
二. 环境
使用masm for windows作为编程工具,使用起来还是很方便的,也可以对汇编代码进行调试,个人觉得比使用VS要简洁一些。
三. 寄存器
AX,BX,CX,DX分别代表四个32位的寄存器。而AH,AL分别代表AX的高32位和低32位,BH,BL,CH,CL,DH,DL同理。
四. 常用指令
move AX,BX (将BX中的值放入AX中)
inc AX (将寄存器AX中的值加1)
jmp XXX (无条件跳转到XXX)
cmp AX,BX (比较AX,BX中的值是否相等)
je XXX (如果相等,则跳转到XXX)
push AX (把AX的值压入栈保护)
pop AX (把AX的值恢复)
add DL,BL (DL=DL+BL)
div操作复杂一些,为了取余,可以参考这篇博文汇编 DIV 指令
四. 整体思路
参照C语言代码的逻辑,分别用两个寄存器存储被除数和除数。外部的循环是被除数不断增加,直到100,判断其是否需要输出,内部循环是除数不断增加,直到被除数本身,遇到可以整除的即可判断其不是质数,便可以结束这个循环。当然,这个过程要变成一个直线化的过程才能写成汇编程序。
五. 难点
这个作业最大的难点竟然不是找质数,而是把找到的数字输出出来。使用了汇编才知道,有printf,cin,cout的日子是多么的幸福。
首先看到的是使用这样的命令进行输出:
mov DL,CH
mov AH,2
int 21H
将要输出的值写入DL寄存器,然后让ah为2,最后调用中断来输出,但是最开始这样做的时候只得到了一串奇怪的字符,查阅资料发现,这样输出的寄存器值对应的ASCII码,无法直接输出数字。
这对于从没有从底层考虑过问题的我确实非常难办,后来咨询了大佬,看了别人的源码才知道应该怎么处理这种情况:
(1)通过对寄存器的值进行调整,把其转化成对应的能输出数字的ASCII值,比如对于1,1+30h得到的就是对应的1的ASCII码,也就可以输出'1'了。
(2)但是这样只能输出一位,那对于多位数字应该怎么处理呢?很简单,取余。比如对于17,17/10 得1余7,7压入栈,1/10得0余1,结束循环,依次输出1和7,我们就得到了17.
六. 源码
DATAS SEGMENT
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
mov BL, 2 ; save i(that is the dividend)
mov BH, 2 ; save j(that is the divider)
mov CL, 1 ; save flag(1 represent isPrime, 0 represent isNotPrime)
ISPRIME:
cmp BL,100 ; check if need to end the program
je STOP ; end the program
cmp BL,BH ; check if divider reach to dividend iteself
je ISCANPRINT ; judge if can print
XOR AX,AX ; empty AX
MOV AL,BL ; move dividend to AL
DIV BH ; AL%BH=AH remainder stored in AH
cmp AH,0 ; Is there any exception
je NOTPRIME ; if AH stores 0, that means the value stored in BL is not prime
inc BH ; increase the divider
jmp ISPRIME ; turn to the beginning and loop
ISCANPRINT:
cmp CL,1 ; check flag
je PRINT ; print the value
mov BH,2 ; if not prime, divider begin again at 2
mov CL,1 ; reset flag
jmp ISPRIME ; turn to the beginning and loop
PRINT:
XOR BH,BH ; empty BH
mov AX,BX ; move value to AX
call PRINTNUMBER ; print the number
NOTPRIME:
inc BL ; increase dividend
mov CL, 0 ; set flag
jmp ISCANPRINT ; turn to judge
PRINTNUMBER proc near
push ax
push bx
push cx
push dx
mov bx,10
mov cx,0
PUSHTOSTACK:
mov dx,0
div bx
push dx
inc cx
cmp ax,0
jz POPFROMSTACK
jmp PUSHTOSTACK
POPFROMSTACK:
pop dx
add dl,30h ; change ASCII to real number
mov ah,2
int 21h
loop POPFROMSTACK
pop dx
pop cx
pop bx
pop ax
mov AH,2
mov DL,0
int 21h
ret
PRINTNUMBER endp
STOP: RET
CODES ENDS
END START
七. 参考资料
感谢各位大佬提供的源码,没有源码的学习,我不可能完成这次作业,掌握汇编的基本知识和解决一些难点。
https://github.com/LLipter/assembly/blob/master/Intel-16/primeNumber.asm