10.5 转移地址在寄存器中的call指令
call 16位reg
相当于执行
push IP
jmp 标号
检测点 10.4
AX=000BH
这个程序稍微修改一下,就很清晰了。
assume cs:codesg codesg segment mov ax,6 ;首先sp=sp-2=fffeh,将IP=5压入栈中 ;跳转到ax处,jmp 6 call ax inc ax ;在bp的段地址是ss,下面两句相当于 ;pop bp ;add ax,bp mov bp,sp add ax,ss:[bp] mov ax,4c00h int 21h codesg ends end
10.6 转移地址在内存中的call指令
call word ptr 内存单元地址
相当于进行
push IP
jmp word ptr 内存单元地址
call dword ptr 内存单元地址
相当于
push CS
push IP
jmp dword ptr 内存单元地址
检测点 10.5
(1)
ax=3
在call word ptr ds:[0eh]时,ax=0,将下一条指令IP压入栈中,然后指令跳到ds:[0eh](实际上就是栈顶),所以实际上就是跳到了inc ax执行,最后得到ax = 3
(2)
assume cs:code data segment dw 8 dup (0) data ends code segment start: mov ax,data mov ss,ax mov sp,16 mov word ptr ss:[0],offset s;将标号s的地址存入ss:[0] mov ss:[2],cs call word ptr ss:[0] ;这里首先将CS=076bh和IP=19h压入栈中 ;再跳转到ss:[0]中的地址处,也就是标号s处 nop s: mov ax,offset s ;将标号s的地址放到ax=001ah sub ax,ss:[0ch] ;ss:[0ch]也就是IP=0019h ;ax=1ah-19h=1 mov bx,cs ;bx=076bh sub bx,ss:[0eh] ;ss:[0eh]也就是CS=076bh ;bx=076bh-076bh=0 mov ax,4c00h int 21h code ends end start
10.7 call和ret的配合使用
问题 10.1
assume cs:code code segment start: mov ax,1 mov cx,3 call s;将下一指令偏移地址压入栈中,然后跳转到标号s mov bx,ax mov ax,4c00h int 21h s: add ax,ax loop s ret;pop IP,跳转到call s指令的下一指令 code ends end start
利用call和ret可以实现子程序机制
10.8 mul指令
(1)计算10*100
assume cs:code code segment start: mov al,100 mov bl,10 mul bl mov ax,4c00h int 21h code ends end start
(2)计算100*10000
assume cs:codesg codesg segment mov ax,100 mov bx,10000 mul bx mov ax,4c00h int 21h codesg ends end
10.9 模块化程序设计
10.10 参数和结果传递问题
assume cs:code data segment dw 1,2,3,4,5,6,7,8 dd 8 dup (0) data ends code segment start: mov ax,data mov ds,ax mov si,0 mov di,16 mov cx,8 s: mov bx,[si] call cube mov [di],ax mov [di].2,dx add si,2 add di,4 loop s mov ax,4c00h int 21h cube: mov ax,bx mul bx mul bx ret code ends end start
10.11 批量数据的传递
将data段的字符串转换为大写
assume cs:codesg datasg segment db 'heyyoubiubiubiu' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov si,0 mov cx,15 call capital mov ax,4c00h int 21h capital: and byte ptr [si],11011111b inc si loop capital ret codesg ends end start
附注4 用栈传递参数
将需要的数据压入栈中,再由子程序出栈
计算(a-b)^3,a=3,b=1
assume cs:codesg stacksg segment db 16 dup (0) stacksg ends codesg segment start: mov ax,stacksg mov ss,ax mov sp,16 mov ax,1 push ax mov ax,3 push ax ;将,1,3分别入栈,ss:[sp]=3, ss:[sp+2]=1 call difcube ;将IP入栈,ss:[sp]=IP, ss:[sp+2]=3, ss:[sp+4]=1 mov ax,4c00h int 21h difcube: push bp ;将bp入栈,ss:[sp]=bp,ss:[sp+2]=IP, ss:[sp+4]=3, ss:[sp+6]=1 mov bp,sp mov ax,[bp+4];3 sub ax,[bp+6];1 mov bx,ax mul bx mul bx pop bp ;bp出栈,ss:[sp]=IP, ss:[sp+2]=3, ss:[sp+4]=1 ret 4 ;IP出栈,ss:[sp]=3, ss:[sp+2]=1,并sp=sp-4=10h codesg ends end start
10.12 寄存器冲突问题
问题 10.2
子程序和主程序循环使用了同一个cx判断,可以将cx入栈
assume cs:codesg datasg segment db 'word',0 db 'unix',0 db 'wind',0 db 'good',0 datasg ends codesg segment start: mov ax,datasg mov ds,ax mov bx,0 mov cx,4 s: push cx mov si,bx call capital pop cx add bx,5 loop s mov ax,4c00h int 21h capital: mov cl,[si] mov ch,0 jcxz ok and byte ptr [si],11011111b inc si jmp short capital ok: ret codesg ends end start
如果我们期望:
- 1.调用子程序时,不必关心子程序的程序使用了哪些寄存器。
- 2.编写子程序时,不必关心调用者使用了哪些寄存器。
- 3.不会发生寄存器冲突
下面这种会更合适
assume cs:codesg datasg segment db 'word',0 db 'unix',0 db 'wind',0 db 'good',0 datasg ends codesg segment start: mov ax,datasg mov ds,ax mov si,0 mov cx,4 s: call capital add si,5 loop s mov ax,4c00h int 21h capital: push cx push si change: mov cl,[si] mov ch,0 jcxz ok and byte ptr [si],11011111b inc si jmp short change ok: pop si pop cx ret codesg ends end start
实验 10 编写子程序
1.显示字符串
步骤:
- 将行列值转换为正确的偏移地址,保存到寄存器(起始偏移地址:(行数 - 1) * 160, 列数偏移地址:(列数 - 1) * 2)
- 读取字符和属性值,判断是否为0,不是,则存入显示缓冲区
assume cs:code data segment db 'Welcome to masm!',0 data ends code segment start: mov dh,8 mov dl,3 mov cl,2 mov ax,data mov ds,ax mov si,0 call show_str mov ax,4c00h int 21h show_str: ;计算行的偏移地址,利用上次得到的结论,起始偏移地址:行数 * 160 mov al,dh mov ah,0 ;dec ax mov bx,160 push dx;因为下面乘法是16位的,因此dx会被用作高位,原来保存颜色的值会消除 mul bx pop dx mov di,ax ;结算列的偏移地址,地址0开始 mov bl,dl mov bh,0 dec bx add bx,bx ;显示缓冲区地址 mov ax,0b800h mov es,ax ;颜色参数值(不变)和字符 mov ah,cl str: mov al,ds:[si] ;检查是否遇到尾部的0 mov cl,al mov ch,0 jcxz ok ;将字符和属性存入显示缓冲区 mov es:[bx+di],al mov es:[bx+di+1],ah inc si;字符偏移地址 add bx,2;列的位置 loop str ok: ret code ends end start
2.解决除法溢出
(1)除数:有8位和16位两种,在一个reg或者内存单元中
(2)被除数:默认放在AX或者DX和AX中,如果除数为8位,被除数为16位,默认放在AX中;如果除数为16位,被除数为32位,在DX和AX中存放,DX存高六位,AX存低六位。
(3)结果:如果除数为8位。则AL储存除法操作的商,AH储存除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数
1000/1溢出,al无法存下1000
assume cs:code code segment start: mov bh,1 mov ax,1000 div bh mov ax,4c00h int 21h code ends end start
11000H/1,ax存放不下商
assume cs:code code segment start: mov ax,1000h mov dx,1 mov bx,1 div bx mov ax,4c00h int 21h code ends end start
解决方法:
实际上,我们解决的方法是将除法拆分成了两个16位除数,32位被除数的除法
例如:1000000/10(F4240H/0AH)
我们将这个除法拆分成:高16位:000FH/0AH 低16位:4240H/0AH 两个除法式子
然后将第一个的结果放到dx,第二个的结果放到ax,余数放到cx
这里唯一不同的就是,我们高16位计算的时候,我们相当于把尾数的0省略了,所以真正结果需要乘上 16^尾数0的个数,上面例子就是16^4=65536。
通过理解 12341000/10 = int(1234/10) * 10^4 + (rem(1234/10) * 10^4 + 1000) / 10 给的公式也就很好理解了
这里还需要知道,溢出的基本概念:超过寄存器表示范围的值被舍弃。
assume cs:code stack segment dw 8 dup (0) stack ends code segment start: mov ax,stack mov ss,ax mov sp,16 mov ax,4240h mov dx,00Fh mov cx,0ah call divdw mov ax,4c00h int 21h divdw: push ax ;高16位计算 mov ax, dx mov dx, 0 div cx mov bx, ax ;低16位计算 pop ax
;高16位计算的余数作为了低16位计算的高位,对应公式的rem(H/N)*65536 div cx ;低16位 mov cx, dx ;余数 mov dx, bx ret code ends end start