《汇编语言(第四版)》---王爽 第十章call和ret指令 详细笔记+代码 ~后续章节笔记,课后检测,实验代码持续更新中

《汇编语言(第四版)》—王爽 第十章call和ret指令

10.1、ret和retf

call和ret指令都是转移指令,它们都是修改IP,或者同时修改CS和IP

ret指令用栈中的数据,修改IP的内容,从而实现近转移

CPU执行ret指令是执行以下操作:

  • (ip) = ((ss) * 16 + (sp)) ;指向栈顶
  • (sp) = (sp) + 2

retf指令用于修改cs和ip的内容,从而实现远转移

CPU在执行retf指令时,进行下面的操作:

  • (ip) = ((ss) * 16 + (sp))
  • (sp) = (sp) + 2
  • (cs) = ((ss) * 16 + (sp))
  • (sp) = (sp) + 2

在这里插入图片描述

可以看出我们用汇编语言来表示ret和retf的时候,就相当于是

ret:pop ip

retf:pop ip pop cs

10.2、call指令

call指令经常和ret指令配合使用,因此CPU之心call指令,进行两部操作

  • 将ip或者cs ip压入栈中
  • 转移(jmp)

call指令不能实现短转移,除此之外,call指令实现转移的方式和jmp指令的原理相同

10.3、依据位移进行转移的call指令

call 标号(将IP压入栈中,然后执行跳转)

  • (sp) = (sp) -2
  • ((ss) * 16 + (sp)) = (ip)
  • (ip) = (ip) + 16位位移

call 标号

16位位移 = “标号”处的地址 - call指令后的第一个字节的地址、

范围为:-32768~32767

16位的位移由编译程序在 编译时算出

10.4、转移的目的地址在指令中的call指令

前面讲解的call指令,其对应的机器码指令中并没有转移的目的地址,而是相当于当前IP的转移位移

指令call far ptr 标号实现的是段间的转移

call far ptr 标号 CPU在执行的时候的操纵:

  • (sp) = (sp) - 2
  • ((ss) * 16 + (sp)) = (cs)
  • (sp) = (sp) - 2
  • ((ss) * 16 + (sp)) = (IP)
  • (cs) = 标号所在的段地址
  • (ip) = 标号所在的偏移地址

((ss) * 16 + (sp)) = (cs)在执行的时候相当于是进行了:

push CS

push IP

jmp far ptr 标号

10.5、转移地址在寄存器中的call指令

指令格式:call 16位寄存器

功能:

  • (sp) = (sp) - 2
  • ((ss) * 16 + (sp)) = (IP)
  • ip = (16位寄存器)

call 16位寄存器的执行就相当于是

push IP

jmp 16位寄存器

10.6、转移地址在内存单元中的call指令有两种格式

call word ptr 内存单元地址

在8086CPU中实现的是段内的短转移

  • push ip
  • jmp word ptr 内存单元地址
mov sp,10H
mov ax,0123H
mov ds:[0],ax
call word ptr ds:[0]
执行之后(IP) = 0123H,(sp) = 0Eh

call dword ptr 内存单元地址

在8086CPU中实现的是段间转移

  • push cs
  • push ip
  • jmp dword ptr 内存单元地址
mov sp,10H
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
执行之后(cs) = 0;(ip) =0123H ;(sp) = 
默认段地址放在高位,偏移地址放在低位

10.7、call和ret的配合使用

举例分析:

assume cs:code 
code segment
start: mov ax,1
	   mov cx,3
	   
	   call s 
	   mov bx,ax
	   mov ax,4C00H
	   int 21H
	s: add ax,ax
	   loop s
	   ret
	   
code ends
end start	

执行过程详细解答

  • CPU将call s指令的机器码读入,IP指向call s后的指令mov bx,ax,然后CPU执行call s指令,将当前的IP值(指令mov bx,ax的偏移地址)压入栈中,将IP的值修改位标号为s处的偏移地址
  • CPU从标号为s处开始执行,loop循环指令结束,此时ax中的数值为8
  • CPU将ret指令的机器码读入,IP指向了ret指令后的内存单元,然后CPU执行ret指令,从栈中他拿出一个值即之前压入栈中的IP的数字,将这个数值送入到IP中,然后继续执行指令mov bx,ax
  • CPU从mov bx,ax开始执行,直到指令结束为止

在这里插入图片描述
在这里插入图片描述

从上面的讨论中我们发现,可以写一个具有一定功能的程序段,我们称其为子程序,在需要的时候,用call 指令转去执行。可是执行完子程序后,如何让CPU 接着call 指令向下执行? call 指令转去执行子程序之前,call 指令后面的指令的地址将存储在栈中,所以可在子程序的后面使用ret 指令,用栈中的数据设置IP的值,从而转到 call 指令后面的代码处继续执行。

在这里插入图片描述

10.8、mul指令

mul是乘法指令使用mul做乘法的时候

  • 相乘的两个数,要么都是八位要么都是十六位
    • 8位:AL中和8位寄存器或者内存单元
    • 16位:AX中和16位寄存器或者内存单元中
  • 结果:
    • 8位:AX中
    • 16位:DX(高位)和AX(低位)中
  • 如果是用8位乘以16位
    • 就是将8位强制转化为16位,高位补0即可
  • 格式如下:
    • mul reg
    • mul 内存单元

内存单元可以用不同的内存方式给出:

  • mul byte ptr ds:[0]
    • 含义是:(ax) = (al) * ((ds) * 16 + 0)
  • mul word ptr [bx + si + 8]
    • 含义是:(ax) = (ax) * ((ds) * 16 + (bx) + (si) + (8))结果的低8位
    • (dx) = (ax) * ((ds) * 16 + (bx) + (si) + (8))结果的高8位

mul使用举例:

  • 计算100 * 10

    • 分析:两个数据都没有超过255,因此使用的是8位的乘法

    • mov al,100
      mov bl,10
      mul bl
      
  • 计算100 * 10000

    • 分析:有一个数据超过了255,则此时需要使用16位的乘法,所以需要将100这个数字转化为16位

    • mov ax,100
      mov bx,10000
      mov bx
      

10.9、模块化程序设计

在之前的学习中,我们看到了,call与ret指令共同支持了汇编语言编程中的模块化设计,在实际的编程中,程序的模块化设计是必不可少的

因为现实的问题比较复杂,对现实问题进行分析时,把它转化成为相互联系、不同层次的子问题,是必须的解决方法。

而 call 与ret 指令对这种分析方法提供了程序实现上的支持。利用call 和 ret 指令,我们可以用简捷的方法,实现多个相互联系、功能独立的子程序来解决一个复杂的问题。

10.10、参数和结果传递的问题

子程序一般都是根据提供的参数处理一定的事务,处理之后,将结果提供给调用者

其实,我们讨论参数和返回值传递的问题,实际上就是在探讨,应该如何存储子程序需要的参数和产生的返回值。

我们设计一个子程序,可以根据提供的N,来计算N的3次方:

存在的问题:

  • 我们将参数N存储在什么地方
  • 计算得到的数值,我们存储在什么地方

在这里插入图片描述

注意,我们在编程的时候要注意形成良好的风格,对于程序应有详细的注释。子程序的注释信息应该包含对子程序的功能、参数和结果的说明。

10.11、批量数据的传递

编程将data段中的字符全部由变为大写字母

assume cs:code

data segment
	db 'conversation'
data ends

code segment

start: mov ax,data
	   mov ds,ax
	   mov si,0
	   
	   mov cx,12
	   call capital
	   mov ax,4C00H
	   int 21H
	   
capital:
	   and byte ptr [si],11011111b
	   inc si
	   loop capital
	   ret
	   
code ends
end start

除了寄存器、内存传递参数外,还有一种通用的方法使用栈来传递参数。

10.12、寄存器冲突问题

设计一个子程序:将一个全是字母的以0为结尾的字符串,转化为大写

分析:这个子程序,字符串的内容后面是一个0,标记着字符串的结束,子程序可以读取每个字符进行,如果不是0则进行转换,如果是0则结束处理

由于可以通过检测0而知道是否已经处理完整的字符串,所以子程序可以不需要字符串的长度作为参数,我们可以直接用JCXZ来检测0

猜你喜欢

转载自blog.csdn.net/weixin_60363168/article/details/127758827
今日推荐