汇编学习教程:灵活寻址(四)

引言

在上篇博文中,我们学习了 [bx+si] 的灵活寻址形式,由此讲解了汇编中的多重循环实现。那么本篇博文中,我们将继续学习灵活寻址其他实现形式。

本次学习从一道编程案例开始学起。

编程示例如下:

assume cs:code,ds:data

data segment
    db '1. file         '
	db '2. edit         '
	db '3. search       '
	db '4. view         '
	db '5. options      '
	db '6. help         '
data ends

题目:编程实现将data段中每个单词的前四个字母改为大写字母。

题目分析

首先我们观察给出的每行数据中,前三位的数据不再是字母,也就意味着我们在进行大小写转换时,需要避开前三位,转换从第4位开始,即下标3。那么我们可以将行偏移 bx 设置为3,即从第4位数据开始访问,列偏移 si 设置为0,这样我们就只访问到每行中的单词前4个字母,然后对其进行大写转换即可。

编程实现

编写代码如下:

assume cs:code,ds:data,ss:stack

data segment
    db '1. file         '
	db '2. edit         '
	db '3. search       '
	db '4. view         '
	db '5. options      '
	db '6. help         '
data ends

stack segment
    dw 0,0,0,0,0,0,0,0
stack ends

code segment
    start:
	    mov ax,data
		mov ds,ax            ; 设置数据段
		mov bx,3             ; 设置行起始偏移为3
		mov cx,6             ; 设置外层循环为6次
		
	s:
	    push cx              ; 将当前外层循环数入栈
		mov cx,4             ; 设置内层循环为4次
		mov si,0             ; 设置列起始偏移为0
	s1:	
        mov al,ds:[bx+si]    ; 取 bx+si 下的字节数据到al寄存器中
		and al,5FH           ; 将al寄存器中的数据与5FH进行且运算,将结果放到al寄存器中
		mov ds:[bx+si],al    ; 将al寄存器中的数据放到 bx+si 字节单元下
		add si,1             ; 列偏移加1,移到下一个字节单元
		loop s1              ; 判断内层循环是否结束
		
		pop cx               ; 将外层循环数出栈,放到CX寄存器内
		add bx,16            ; 行偏移加16,移到下一行
		loop s               ; 判断外层循环是否结束
		
	mov ax,4c00H
	int 21H
code ends
end start

由于我们初始化将bx偏移位置设置为3,且每一行的长度都是16行,那么bx加上16后,所处的偏移位置是第二行的下标3处,这样就保证了第二行起始位置是英文单词

我们将上述代码编译连接后,在Debug内进行运行调试,观察执行情况:

 首先我们设置完数据段后,使用d命令查看当前数据段中的数据如上图,接下来我们使用t命令加p命令,结束双层循环后,查看此时数据段中的数据如下图:

可以看到,我们编码实现了题目要求,将每行中每个单词的前4个字母转换成了大写。

疑问

看到这里你可能会存在疑问,在上述编码实现中,我们使用的是 [bx+si] 形式来做的,该形式是上篇博文中的所学内容,那么本篇博文中我们是要学习新的寻址形式的,既然 [bx+si] 已经可以解决问题,还学什么新寻址呢?

其实不尽然,[bx+si] 虽然已经功能强大,但是在某些情况下,还是会面临着局限性。比如,我们将上述题目中的数据做出以下修改:

assume cs:code,ds:data

data segment
    db '1. file         '
	db 'edit            '
	db '3. search       '
	db '4. view         '
	db 'options         '
	db '6. help         '
data ends

题目:编程实现将data段中每个单词的前四个字母改为大写字母。

观察题目你就会发现,给出的数据中每一行并不在是一个统一的格式,其中第2行、第5行数据,与其他四行数据格式并不相同。那么此时我们使用 [bx+si] 是否还可行呢?答案是当然不行了!

如果使用 [bx+si] 寻址形式,bx的起始偏移位置就是一大难题,如果设置bx起始偏移为0的话,那么第一行、三行、四行、六行的寻址将会发生错误,毕竟它们的起始位置并不是英文字母。当然我们可以通过判断0偏移处的字节数据是否是一个字母来解决这样的问题,但是我们目前还没有学习到判断,所以就需要另外的方式来处理。

使用 [bx+si+idata]

[bx+si+idata] 也是表示一个内存单元,那么该内存单元的偏移地址为:bx中的值加上si中的值再加上一个自然数idata

指令:mov ax,[bx+si+idata]表示为:

将段地址为DS,偏移地址为 bx 中的值加上 si 中的值 再加上一个自然数 idata下的字单元数据,送入寄存器AX中。

数学化描述为:(ax) = ((ds)*16+(bx)+(si)+idata)

由于 idata 是一个固定的自然数,无法在程序运行中改变,所以 [bx+si+idata] 的寻址方式相比较 [bx+si] 的寻址方式,灵活程度并未大幅度提升。那这里你可能就要疑问了,既然灵活程度上提升不大,那为什么还要再增加一个这样的寻址呢?它的意义在什么地方?

我们知道遍历一个类二维数组的内存空间,我们使用 bx 来定位每行数据的起始地址,通过si自增,来使用 si 来遍历每行中的每列数据。那么增加的自然数 idata ,则相当于定位了列的起始地址

示例1

就拿本篇博文开头的编程示例来说。

data segment
    db '1. file         '
	db '2. edit         '
	db '3. search       '
	db '4. view         '
	db '5. options      '
	db '6. help         '
data ends

我们观察发现,每一行中,第4列开始才是英文字母,在示例中我们通过设置行的起始位置bx为3来完成了寻址。这里使用 [bx+si+idata] 来做,行的起始位置bx为0保持不变,设置列的起始位置为3,即 idata 为3,那么我们照样可以完成寻址。

代码只需改动几处:

assume cs:code,ds:data,ss:stack

data segment
    db '1. file         '
	db '2. edit         '
	db '3. search       '
	db '4. view         '
	db '5. options      '
	db '6. help         '
data ends

stack segment
    dw 0,0,0,0,0,0,0,0
stack ends

code segment
    start:
	    mov ax,data
		mov ds,ax
		mov bx,0              ; 设置行的起始偏移为0
		mov cx,6
		
	s:
	    push cx
		mov cx,4
		mov si,0
	s1:	
        mov al,ds:[bx+si+3]   ; 偏移再加上3,确定列的起始偏移
		and al,5FH
		mov ds:[bx+si+3],al
		add si,1
		loop s1
		
		pop cx
		add bx,16
		loop s
		
	mov ax,4c00H
	int 21H
code ends
end start

该代码和上面的代码改动处就在于,首先我们将bx的值重新归0,即行的起始位置回归0;然后将 [bx+si] 改为 [bx+si+idata],其中 idata 即列的起始位置,我们设置为3,这样在寻址时就会从每行中的第四列开始,确定寻到的数据为英文字母。

我们将上述代码编译连接后,在debug中运行调试,使用d命令查看结果:

可以看到我们将前四个英文字母变成了大写,使用 [bx+si+idata] 寻址完成了要求结果。

思考

在上述示例中,我们虽然明白了 [bx+si+idata] 的使用,但是还是存在一丝疑惑,那就是 idata 似乎并没有为寻址提供了多少便利,尤其是在本篇开头的示例中,我们直接使用 [bx+si] 的寻址方式照样完成,idata 成为了可有可无的存在。那么存在 [bx+si+idata] 它到底是为了什么?

首先这里说明,在 [bx+si+idata] 寻址方式中,idata的主要功能是定位二维数组中每行中的寻址起始位置。例如上述实例中,每行的数据需要从第3位开始,所以idata值便设置为3即可。

你会反驳说这个起始位3,我们可以设置si起始为3或者去设置bx,不需要再来一个idata来凑热闹呀~当然,如果你面临的是上述的示例,你完全可以这样做没问题,只是不符合编程规范而已。

注意,汇编语言虽然是低级语言,它也是有自己的编程规范的,对于内存访问场景,规范要求应该尽量使 bx 寄存器、si 寄存器、di 寄存器等一些值初始为0,而不要赋值一个非0的初始值

 为什么要有这样的规范呢?那是因为寄存器初始值为0符合绝大多数的访问内存场景要求,如果你对此很疑惑,证明你在汇编的开发上比较少。下面,我们通过一个示例,将深入的理解 [bx+si+idata] 的使用场景,领略为何有如此规范的用意!

示例2

data segment
    db '1. file         '
	db '2. edit         '
	db '3. search       '
	db '4. view         '
	db '5. options      '
	db '6. help         '
data ends

编程实现将上述数据段中的,提取每行英文单词的前四个英文字母,将其转换大写后按照每行的排列顺序,写到内存起始地址20000H下。

根据题目思考,首先转换英文大小写我们已经很熟悉了,如何将转换后的数据按行写到新的地址下似乎有点难度。如果使用 [bx+si] 的寻址方式,很明显我们需要设置bx的初始值或者si的初始值为3才能正确寻址到源数据进行处理。但是这样,在写入目的地址的时候却变得很不合理,因为目的地址的起始偏移为0,如果bx的初始值或者si的初始值为3,这样将会导致无法正确写入目的地址

根据我们的逻辑,源数据的处理和写入目的地址,是在同一个循环下进行,也就是说,寻址源地址和寻址目的地址是要使用相同的偏移地址。因为要求写入的目的地址的起始偏移位置是0,那么就意味着源地址起始偏移也是0才行,这样才保证了两边数据访问的一致性。所以,在该示例中,肯定不能将bx的初始值或者si的初始值设置为3,它俩初始值只能为0!你总不可能说在处理完源数据后,再修改一次bx、si的值来保证写入目的地址正确,这样做只会增加逻辑复杂度,同时降低程序的运行效率!

既然如此,显然使用 [bx+si] 的寻址方式是不符合要求了~使用 [bx+si+idata] 才是解决之道!

编程实现代码如下:

assume cs:code,ds:data,ss:stack

data segment
    db '1. file         '
	db '2. edit         '
	db '3. search       '
	db '4. view         '
	db '5. options      '
	db '6. help         '
data ends

stack segment
    dw 0,0,0,0,0,0,0,0
stack ends

code segment
    start:
	    mov ax,data
		mov ds,ax  	
        mov ax,2000H		
        mov es,ax		        ; 设置es段地址为2000H,做为目的地址的段地址
		mov bx,0                ; 因为目的地址的起始偏移位置为0,所以bx初始为0
		mov cx,6           
		
	s:
	    push cx              
		mov cx,4             
		mov si,0                ; 设置每行的起始偏移为0              
	s1:	
        mov al,ds:[bx+si+3]     ; 使用[bx+si+idata]寻址,设置每行的起始位置为3
		and al,5FH   
        mov es:[bx+si],al       ; 写入目的地址,偏移为bx+si
		add si,1             
		loop s1              
		
		pop cx               
		add bx,16            
		loop s               
		
	mov ax,4c00H
	int 21H
code ends
end start

我们看上述编程实现,其中关键的地方是,我们在寻址源数据的时候,通过 [bx+si+idata] 的形式设置每行的起始位置,这样保证了bx和si初始值为0,在数据写入目的地址时,就可以直接使用bx和si来做为目的地址的偏移地址,确保数据正确写入指定位置

我们将上述代码编译连接后,在debug中运行调试,使用d命令查看地址2000H:0H~2000H:5FH的数据:

 可以看到我们成功将每行英文单词的前四个因为字母转换大写后,将其按照每行的排列顺序写到了指定目的地址下。

总结

[bx+si+idata] 的寻址方式,灵活性体现在:

它通过一个自然数idata来指定在二维数组的寻址中每行的起始位置,使bx、si\di 寄存器无需赋值一个非零初始值,这样保证了在数据复制场景中,源地址和目的地址的偏移地址一致性,提高了内存访问效率。

 通过示例2中的编程展示,相信你已经意识到了 [bx+si+idata] 存在的意义,后面在面临复杂的访问内存场景,亦能做到选择适合的寻址方式来处理。

本篇结束语

在本篇博文中,我们学习了一个全新的灵活寻址方式:[bx+si+idata],了解了idata在其的作用和含义,通过具体的示例分析和与 [bx+si] 寻址方式的对比,深入刨析了 [bx+si+idata] 寻址方式的意义所在。

截止到现在,我们已经学习了众多灵活寻址方式,有[bx]、[bx+idata]、[bx+si]、[bx+si+idata] 等等,那么在下篇博文中,我们将对这些寻址方式做一个归纳总结,来加深我们的印象。

感谢围观,转发分享请标明出处,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/20826.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【jvm系列-12】jvm性能调优篇---GUI工具的基本使用

JVM系列整体栏目 内容链接地址【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963【三】运行时私有区域之虚拟机栈…

openEuler 成功适配 LeapFive InFive Poros 开发板

近日,openEuler RISC-V 23.03 创新版本在跃昉科技的 Poros 开发板上成功运行。 openEuler 在 Poros 上适配成功,XFCE 桌面启动正常,文件系统、终端模拟器和输入法等相关 GUI 应用也运行流畅,Chromium 浏览器和 LibreOffice 等应用…

大屏只用来做汇报?知道这6个应用场景,直接升职加薪!

五一假几个朋友小聚了一下,好久没联系了,现在才知道大家从事行业五花八门的。知道我从事IT行业好几年,他们非要让我讲讲现在异常火爆的大屏,说是所在企业单位都在研究这玩意儿,有的业务人员焦虑不已不知道如何下手&…

SD-如何训练自己的Lora模型

官方地址:GitHub - bmaltais/kohya_ss 尝试过mac和Ubuntu,装上后都会有问题 Windows按照官方步骤安装即可 第一步 git clone https://github.com/bmaltais/kohya_ss.git cd kohya_sspython -m venv venv .\venv\Scripts\activatepip install torch1.…

SpringCloud Alibaba详解

目录 微服务架构概念 服务治理 服务调用 服务网关 服务容错 链路追踪 SpringcloudAlibaba组件 Nacos 负载均衡 Ribbon Fegin Sentinel 高并发测试 容错方案 Sentinel入门 Feign整合Sentinel 微服务架构概念 服务治理 服务治理就是进行服务的自动化管理&#xf…

pod的基本介绍| harbor仓库的搭建 tomcat镜像拉取

pod的基本介绍| harbor仓库的搭建 tomcat镜像拉取 一 Pod基础概念:二 通常把Pod分为两类:三 Pod容器的分类:四 应用容器(Maincontainer)五 镜像拉取策略(image PullPolicy)六 部署 harbor 创建私…

SpringMVC高手进阶

🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎 📚系列专栏:Java全栈,…

解密Netty中的Reactor模式

文章目录 单线程Reactor模式多线程Reactor模式Reactor模式中IO事件的处理流程Netty中的通道ChannelNetty中的反应器ReactorNetty中的处理器HandlerNetty中的通道Channel和处理器Handler的协作组件Pipeline Reactor(反应器)模式是高性能网络编程在设计和架构方面的基础模式.Doug…

Science文章复现(Python):图1 - Aircraft obs(机载的观测 CO2)

之前有写过science文章后处理的复现Science文章复现(Python):在机载观测中明显的强烈南大洋碳吸收 在这里是针对图细节的理解: 首先需要下载这个项目 https://github.com/NCAR/so-co2-airborne-obs 这里的环境配置会比较麻烦 con…

00后卷起来,真没我们老油条什么事了···

都说00后躺平了,但是有一说一,该卷的还是卷。 这不,前段时间我们公司来了个00后,工作没两年,跳槽到我们公司起薪20K,都快接近我了。后来才知道人家是个卷王,从早干到晚就差搬张床到工位睡觉了。…

【刷题之路】LeetCode 232. 用栈实现队列

【刷题之路】LeetCode 232. 用栈实现队列 一、题目描述二、解题1、图解主要思路2、先实现栈3、实现各个接口3.1、初始化接口3.2、入队接口3.3、出队接口3.4、取队头接口3.5、判空接口3.6、释放接口 一、题目描述 原题连接: 232. 用栈实现队列 题目描述:…

网站测试的主要方法

网站测试的主要方法 网站测试是保证网站质量的重要手段,通过对网站进行测试可以及时发现问题并修复,提高用户体验和网站的可靠性。本文将介绍网站测试的主要方法。 1.功能测试:测试网站的所有功能是否正常。通过模拟用户的操作,确…

在外包干了三年,我废了……不吹不黑!

没错,我也干过外包,一干就是三年,三年后,我废了…… 虽说废的不是很彻底,但那三年我几乎是出差了三年、玩了三年、荒废了三年,那三年,我的技术能力几乎是零成长的。 说起这段三年的外包经历&a…

文档管理-gitlab+markdown网页插件

特点 使用git进行版本管理,本地编辑使用Typora。使用gitlab进行权限管理可以在线阅读通过Markdown在线阅读插件实现,可显示目录显示与链接跳转,界面优于自带的wiki。 与其他方式对比 gitlab的wiki:显示界面效果不好&#xff0c…

【数据结构】栈及其实现

目录 🤠前言 什么是栈? 栈的定义及初始化 栈的定义 栈的初始化 栈的判空 栈顶压栈 栈顶出栈 栈的数据个数 栈的销毁 完整代码 总结 🤠前言 学了相当长一段时间的链表,总算是跨过了一个阶段。从今天开始我们将进入栈和…

[IOT物联网]Python快速上手开发物联网上位机程序——前言

一、什么是Python Python是一种简单易学、高级、通用的编程语言。它是一种解释型语言,不需要编译即可运行,因此可以快速地进行开发和测试。Python具有简洁优美的语法,使用它可以提高生产力和代码可读性。Python拥有强大的标准库和第三方库&am…

Linux Shell 实现一键部署virtualbox

VirtualBox 前言 VirtualBox 是一款开源虚拟机软件。VirtualBox 是由德国 Innotek 公司开发,由Sun Microsystems公司出品的软件,使用Qt编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。Innotek 以 GNU General Public Licens…

孙鑫VC++第四章 1.简单绘图-MFC消息映射机制

1. MFC消息映射机制 接下来将剖析MFC消息映射机制,探讨发送给窗口的消息是如何被MFC框架通过窗口句柄映射表和消息映射表来用窗口类的处理函数进行响应的。另外,还将讲述“类向导”这一工具的运用,讨论设备描述表及其封装类CDC的使用&#x…

玩转ChatGPT:魔改文章成果格式

一、写在前面 首先,我让小Chat替我吐槽一下: 科研人员天天都在填各种表格,简直成了我们的“表格王子”和“表格公主”。从申请项目、提交论文到汇报成果,表格无处不在。我们填表格的时候总是期待着它能让我们的工作更高效、更顺…

遇到一个同事,喜欢查其他同事的BUG,然后截图发工作大群里,还喜欢甩锅,该怎么办?...

职场上都有哪些奇葩同事? 一位网友吐槽: 遇到一个同事,喜欢查同级别同事的bug,截图发工作群,甚至发大群里,还喜欢甩锅,该怎么办? 职场工贼,人人喊打,网友们纷…