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

news2024/11/19 4:44:48

引言

在上篇博文中,我们学习了 [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] 等等,那么在下篇博文中,我们将对这些寻址方式做一个归纳总结,来加深我们的印象。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Jmeter第二章】将Jmeter界面切换为中文显示

1、Jmeter临时切换为中文显示 注意:上面的配置只能保证本次运行是中文,如果要永久中文,需要修改Jmeter的配置文件 2、通过修改Jmeter配置文件设置为中文显示 1、在 Jmeter/bin目录下,找到:jmeter.properties 文件 2…

K_A39_012 基于STM32驱动W25Q32 模块读写数据 串口+OLED0.96显示

K_A39_012 基于STM32驱动W25Q32 模块读写数据 串口OLED0.96显示 所有资源导航一、资源说明二、基本参数参数引脚说明 三、驱动说明时序对应程序: 四、部分代码说明1、接线引脚定义1.2、STM32F103C8T6W25Q32 模块 五、基础知识学习与相关资料下载六、视频效果展示与程序资料获取…

LeetCode 429. N 叉树的层序遍历

429. N 叉树的层序遍历 描述 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。 示例 示例1 输入&…

货拉拉Java开发实习

目录 1.Java的重载和重写有什么区别2.什么情况下需要用到重载3.有很多个字符串和变量,需要把它们加起来,这时候用String会有什么问题4.有没有其它的替代方案5.StringBuffer和StringBuilder有什么区别6.一个自定义对象,分别创建了两个实例&…

5分钟梳理银行测试,文末附带实战项目

很多银行招聘都要求有相关从业经验,这对于想跨入这个岗位的0经验从业同学可真犯了难 “你都不让我上岗,我哪来的工作经验呢?” 为了解决这个问题,小柠檬整理了本篇文章,从3个方面介绍银行项目是如何进行测试的 银行…

springboot---IoC 和 AOP

目录 引语IoC传统开发模式的弊端控制反转和依赖注入 AOP面向对象的局限性面向切面编程 总结 引语 Inversion of Control,缩写为IoC:控制反转 Aspect-oriented programming,缩写为AOP:面向切面编程 IoC和AOP是spring框架最核心的…

VMware Workstation 与 Device/Credential Guard 不兼容.在禁用 Device/Credenti

这个时候我们需要去关掉几个功能 1、关闭Hyper-V 打开控制面板首页,找到“程序”,然后找到“启用或关闭Windows功能”,找到“Hyper-V”,有勾中的全部都取消掉,如果这一步操作失败,不要紧,继续…

使用马哈鱼SQLFlow分析聚合函数中的数据流列

聚合函数通常将列作为参数,在本文中,我们将讨论在用作函数参数的列和聚合函数之间创建什么样的数据流。 1. COUNT() COUNT()可以采用COUNT(),也可以采用任何列名,甚至可以采用空参数。如果参数为空或为列,则参数和函…

DatenLord前沿技术分享 No.25

达坦科技专注于打造新一代开源跨云存储平台DatenLord,通过软硬件深度融合的方式打通云云壁垒,致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题,以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。在本周…

【已解决】使用Arduino调试ARM时编译错误error: ordered comparison of pointer with integer zero的解决方法

在使用Arduino的资源库对STM32编程时,出现: error: ordered comparison of pointer with integer zero (byte* {aka unsigned char*} and int) 编译错误的解决方法。 Arduino因其开源和易用性,丰富的三方资源,受到很多人的喜欢…

Android无线调试

1、首先在系统环境变量——》新建——》"ANDROID_ADB_SERVER_PORT",值:手机的端口号 2、通过adb kill-server,adb start-server,重启abd 3、最后使用:adb connect ip:port(如:192.16…

【SpringBoot】SpringBoot 纯后端项目如何自定义异常页面(Whitelabel Error Page)

文章目录 背景安排方案步骤 验证 背景 一个短链服务,业务将长链接给我,我转换成短地址,用户访问短地址时,我再做redirect;没有前端,纯后端项目短链会有过期时间,过期后将返回错误信息某一天一个…

GPT 专业应用:如何让GPT策划方案

身为一名职场打工人,或多或少会面临需要写策划案的难题。 不管是策划一场线下活动,还是策划业务发展的方向; 甚至到生活中还需要策划婚礼,策划房屋装修,策划和朋友的聚会等等。那么如何快速积累经验,找准…

JavaScript全解析——Ajax教程(上)

AJAX 是Asynchronous JavaScript And XML的缩写。 它不是一种编程语言。它是一种基于HTML、CSS、JavaScript 和 XML,让开发更好、更快和更有互动的 Web 应用的技术。 什么是ajax 认识前后端交互 前后端交互就是前端与后端的一种通讯方式,主要使用的技…

关于一个C++项目:高并发内存池的开发过程(二)

文章目录 内存释放操作的总述thread cachecentral cachepage cachecentral cache的TODO实现何时维护这张映射表? tc_dealloc的修改申请大内存的适配写在最后 上篇文章梳理了内存申请操作的流程,大概测试了一下,没有发现什么问题。这篇文章将梳…

Simulink 自动代码生成电机控制:软件在环测试(SIL)步骤总结

目录 前言 模型配置 SIL模型生成 模型仿真对比 总结 前言 电机模型仿真可以叫做模型在环测试(MIL),至于SIL就是软件在环仿真测试,说白了就是验证生成的代码有没有问题,如果有问题那在模型里面修复,不要…

点餐小程序实战教程05-点餐功能开发

目录 1 点餐需求分析2 变量定义3 点餐分类功能实现4 菜品展示功能开发5 实现切换分类时过滤数据总结我们上一篇设计了点餐分类及点餐信息数据源的功能,本篇我们介绍一下如何开发点餐功能。 1 点餐需求分析 看一下页面是分为两部分,左侧是侧边栏导航,用来展示点餐的分类信息。…

论文解读 | 《基于采样的MPC控制的约束视觉》

原创 | 文BFT机器人 引言 Introduction 视觉伺服控制方案,如基于图像的(IBVS),基于姿态的(PBVS)或基于混合的(HBVS),在过去的几十年里得到了广泛的发展。众所周知,要处理的主要问题涉及局部极小点或奇异点的存在、可见性约束、联合…

缺少ssl模块

nginx采用源码安装方式 1、 查看是否有模块,如下没有 /usr/local/nginx/sbin/nginx -V1.1、 备份nginx配置文件 cp -a nginx.conf nginx.conf.bak2、 进nginx安装包目录 ./configure --prefix/usr/local/nginx --with-http_stub_status_module --with-http_ssl_mo…

将 NGINX 部署为 API 网关

现代应用架构的核心是 HTTP API。HTTP 支持快速构建和轻松维护应用。HTTP API 提供了一个通用接口,因此不必考虑应用的规模大小,无论是单独用途的微服务还是大型综合应用。 HTTP 不仅可以支持超大规模互联网,也可用于提供可靠和高性能的 API …