【汇编语言】数据处理的两个基本问题(三) —— 汇编语言的艺术:从div,dd,dup到结构化数据的访问

文章目录

  • 前言
  • [1. div指令](#1. div指令)
    • [1.1 使用div时的注意事项](#1.1 使用div时的注意事项)
    • [1.2 使用格式](#1.2 使用格式)
    • [1.3 多种内存单元表示方法进行举例](#1.3 多种内存单元表示方法进行举例)
    • [1.4 问题一](#1.4 问题一)
    • [1.5 问题一的分析与求解](#1.5 问题一的分析与求解)
      • [1.5.1 分析](#1.5.1 分析)
      • [1.5.2 程序实现](#1.5.2 程序实现)
    • [1.6 问题二](#1.6 问题二)
    • [1.7 问题二的分析与求解](#1.7 问题二的分析与求解)
      • [1.7.1 分析](#1.7.1 分析)
      • [1.7.2 程序实现](#1.7.2 程序实现)
  • [2. 伪指令 dd](#2. 伪指令 dd)
    • [2.1 什么是dd?](#2.1 什么是dd?)
    • [2.2 问题三](#2.2 问题三)
    • [2.3 问题三的分析与求解](#2.3 问题三的分析与求解)
      • [2.3.1 分析](#2.3.1 分析)
      • [2.3.2 程序实现](#2.3.2 程序实现)
  • [3. dup](#3. dup)
    • [3.1 什么是dup?](#3.1 什么是dup?)
    • [3.2 举例说明](#3.2 举例说明)
    • [3.3 为什么dup有用呢?](#3.3 为什么dup有用呢?)
  • [4. 实验:寻址方式在结构化数据访问中的应用](#4. 实验:寻址方式在结构化数据访问中的应用)
    • [4.1 实验问题描述](#4.1 实验问题描述)
    • [4.2 实验提示](#4.2 实验提示)
  • [5. 解决实验](#5. 解决实验)
    • [5.1 考虑几个问题](#5.1 考虑几个问题)
    • [5.2 初始化阶段](#5.2 初始化阶段)
    • [5.3 每次循环要执行的任务](#5.3 每次循环要执行的任务)
      • [5.3.1 存放年份](#5.3.1 存放年份)
      • [5.3.2 存放公司总收入](#5.3.2 存放公司总收入)
      • [5.3.3 存放公司人数](#5.3.3 存放公司人数)
      • [5.3.4 计算人均收入并存放](#5.3.4 计算人均收入并存放)
    • [5.4 为下一次循环时存放数据做准备](#5.4 为下一次循环时存放数据做准备)
    • [5.5 完整的程序实现](#5.5 完整的程序实现)
  • 结语

前言

📌

汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。

本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。

1. div指令

1.1 使用div时的注意事项

div是除法指令(division),使用div做除法的时候应注意以下问题。

(1)除数:有8位和16位两种,在一个reg(寄存器)或内存单元中。

(2)被除数 :默认放在AX DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。

(3)结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

1.2 使用格式

格式如下:

assembly 复制代码
 div reg
 div 内存单元

1.3 多种内存单元表示方法进行举例

现在,我们可以用多种方法来表示一个内存单元了,比如下面的例子:

(1)

assembly 复制代码
div byte ptr ds:[0]  

含义:

(al) = (ax) / ((ds)*16+0)的商

(ah) = (ax) / ((ds)*16+0)的余数

(2)

assembly 复制代码
div word ptr es:[0]

含义:

(ax) = [(dx)*10000H+(ax)] / ((es)*16+0)的商

(dx) = [(dx)*10000H+(ax)] / ((es)*16+0)的余数

(3)

assembly 复制代码
div byte ptr [bx+si+8] 

含义:

(al)= (ax) / ((ds)*16+(bx)+(si)+8)的商

(ah)=(ax) / ((ds)*16+(bx)+(si)+8)的余数

(4)

assembly 复制代码
div word ptr [bx+si+8] 

含义:

(ax)=[(dx)*10000H+(ax)] / ((ds)*16+(bx)+(si)+8)的商

(dx)=[(dx)*10000H+(ax)] / ((ds)*16+(bx)+(si)+8)的余数

1.4 问题一

编程:利用除法指令计算100001/100。

1.5 问题一的分析与求解

1.5.1 分析

我们首先分析一下,被除数 100001 大于65535,不能用ax寄存器存放,所以我们要用dx和ax两个寄存器联合存放100001,也就是说要进行16位的除法。

除数100小于255,可以在一个 8位寄存器中存放。

但是,因为被除数是32位的,除数应为16位,所以要用一个16位寄存器来存放除数100。

因为要分别为dx和ax赋100001的高16位值和低16位值,所以应先将100001表示为十六进制形式:186A1H。

1.5.2 程序实现

程序如下:

assembly 复制代码
mov dx,1
mov ax,86A1H  	;(dx)*10000H+(ax)=100001
mov bx,100
div bx

程序执行后,(ax)=03E8H(即1000),(dx)=1(余数为1)。大家可自行在Debug中实践。

1.6 问题二

编程:利用除法指令计算1001/100。

1.7 问题二的分析与求解

1.7.1 分析

我们首先分析一下被除数1001可用 ax寄存器存放,除数100可用 8位寄存器存放,也就是说,要进行8位的除法。

1.7.2 程序实现

程序如下:

assembly 复制代码
mov ax,1001
mov bl,100
div bl

程序执行后,(al)=0AH(即10),(ah)=1(余数为1)。大家可自行在Debug中实践。

2. 伪指令 dd

2.1 什么是dd?

前面我们用db和dw定义字节型数据和字型数据。

dd是用来定义dword (double word双字)型数据的。

比如:

assembly 复制代码
data segment
	db 1
	dw 1
	dd 1
data ends

在data段中定义了3个数据:

  • 第一个数据为01H,在data:0处,占1个字节

  • 第二个数据为0001H,在data:1处,占1个字

  • 第三个数据为00000001H,在data:3处,占2个字

2.2 问题三

用div 计算data段中第一个数据除以第二个数据后的结果,商存放在第3个数据的存储单元中。

assembly 复制代码
data segment
	dd 100001
	dw 100
	dw 0
data ends

2.3 问题三的分析与求解

思考后看分析。

2.3.1 分析

data段中的第一个数据是被除数,为dword(双字)型,32位,所以在做除法之前,用dx和ax存储。

应将data:0字单元中的低16位存储在 ax中,data:2字单元中的高16位存储在dx中。

2.3.2 程序实现

程序如下:

assembly 复制代码
mov ax,data
mov ds,ax
mov ax,ds:[0]        	;ds:0字单元中的低16位存储在ax中
mov dx,ds:[2]        	;ds:2字单元中的高16位存储在dx中
div word ptr ds:[4] 	;用dx:ax中的32位数据除以ds:4字单元中的数据	
mov ds:[6],ax         	;将商存储在ds:6字单元中

3. dup

3.1 什么是dup?

dup是一个操作符 ,在汇编语言中同db、dw、dd 等一样,也是由编译器识别处理的符号。

它是和db、dw、dd 等数据定义伪指令配合使用的,用来进行数据的重复。

3.2 举例说明

(1)

assembly 复制代码
db 3 dup (0) 		

定义了3个字节,它们的值都是0,相当于db 0,0,0

(2)

assembly 复制代码
db 3 dup (0,1,2) 

定义了9个字节,它们是 0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2

(3)

assembly 复制代码
db 3 dup ('abc','ABC')

定义了18个字节,它们是 'abcABCabcABCabcABC',相当于db 'abcABCabcABCabcABC'

可见,dup的使用格式如下:

  • db 重复的次数 dup (重复的字节型数据)

  • dw 重复的次数 dup (重复的字型数据)

  • dd 重复的次数 dup (重复的双字型数据)

3.3 为什么dup有用呢?

dup是一个十分有用的操作符,比如我们要定义一个容量为 200 个字节的栈段,如果不用dup,则必须用这样的格式:

assembly 复制代码
stack segment
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

当然,你可以用dd,使程序变得简短一些,但是如果要求定义一个容量为1000字节或10000字节的呢?

如果没有dup,定义部分的程序就变得太长了,有了dup就可以轻松解决。如下:

assembly 复制代码
stack segment 
	db 200 dup (0) 
stack ends 

4. 实验:寻址方式在结构化数据访问中的应用

这个程序是到目前为止我们遇到的最复杂的程序,它几乎用到了我们之前学过的所有知识和编程技巧。

4.1 实验问题描述

Power idea 公司从1975年成立一直到1995年的基本情况如下。

下面的程序中,已经定义好了这些数据:

assembly 复制代码
assume cs:codesg
data segment
db '1975','1976','1977','1978','1979',1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示21年的21个字符串。

dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年公司总收入的21个dword型数据

dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示21年公司雇员人数的21个word型数据

data ends

table segment
	db 21 dup('year summne ??')
table ends

编程,将data段中的数据按如下格式写入到table段中,并计算21年中的人均收入(取整),结果也按照下面的格式保存在 table 段中。

4.2 实验提示

提示,可将data段中的数据看成是多个数组,而将table中的数据看成是一个结构型数据的数组,每个结构型数据中包含多个数据项。

可用bx定位每个结构型数据,用idata定位数据项,用si定位数组项中的每个元素,对于table中的数据的访问可采用[bx].idata和[bx].idata[si]的寻址方式。

5. 解决实验

5.1 考虑几个问题

(1)源数据在哪里?

年份(0-53H)、收入(54H-0A7H)、雇员(0A8H-0D1H)的存放位置

(2)目标存放位用什么来表示?

年份、收入、雇员、平均收入的存放位置

(3)观察数据类型和如何利用偏移寻址?

5.2 初始化阶段

assembly 复制代码
mov ax,data
mov ds,ax
mov ax,table 	;这里因为data已被占用
mov es,ax 

mov bx,0
mov si,0
mov di,0
mov cx,21   	;二十一次循环,大家可想而知

5.3 每次循环要执行的任务

  • 存放年份

  • 存放公司总收入

  • 存放公司人数

  • 计算人均收入并存放

5.3.1 存放年份

assembly 复制代码
mov al,[bx]
mov es:[di],al
mov al,[bx+1]
mov es:[di+1],al
mov al,[bx+2]
mov es:[di+2],al
mov al,[bx+3]
mov es:[di+3],al

5.3.2 存放公司总收入

assembly 复制代码
mov ax,54h[bx]     ;第一个'年收入'的偏移地址为54H
mov dx,56h[bx]
mov es:5h[di],ax
mov es:7h[di],dx

5.3.3 存放公司人数

assembly 复制代码
mov ax,0A8h[si]    ;第一个'人数'的偏移地址为0A8H
mov es:0Ah[di],ax

5.3.4 计算人均收入并存放

assembly 复制代码
mov ax,54h[bx]
mov dx,56h[bx]  			;这两句是初始化被除数

div word ptr ds:0A8h[si] 	;除以人数
mov es:0dh[di],ax     		;将商放入指定位置

5.4 为下一次循环时存放数据做准备

assembly 复制代码
add bx,4  	;bx确定年份和收入
add si,2   	;si确定人数
add di,16 	;di确定的是每行的列数

5.5 完整的程序实现

assembly 复制代码
assume cs:codesg,ds:data,es:table

data segment
        db '1975','1976','1977','1978','1979','1980','1981','1982','1983'

        db '1984','1985','1986','1987','1988','1989','1990','1991','1992'

        db '1993','1994','1995'

        ;以上是表示21年的21个字符串


        dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514

        dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000

        ;以上是表示21年公司总收的21个dword型数据


        dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226

        dw 11542,14430,45257,17800

        ;以上是表示21年公司雇员人数的21个word型数据
data ends


table segment
        db 21 dup('year summ ne ?? ')
table ends


codesg segment
start:

        mov ax,data

        mov ds,ax

        mov ax,table

        mov es,ax

 

        mov bx,0

        mov si,0

        mov di,0

        mov cx,21

 

        s:   ;进入循环

                mov al,[bx]

                mov es:[di],al

                mov al,[bx+1]

                mov es:[di+1],al

                mov al,[bx+2]

                mov es:[di+2],al

                mov al,[bx+3]

                mov es:[di+3],al

                ;以上8句的作用是存放年份

 

                mov ax,54h[bx]     ;第一个'年收入'的段基址为54H

                mov dx,56h[bx]

                mov es:5h[di],ax

                mov es:7h[di],dx

                ;以上4句的作用是存放公司总收入

 

                mov ax,0A8h[si]    ;第一个'人数'的段基址为0A8H

                mov es:0Ah[di],ax

                ;以上2句是存放公司的人数

 

                mov ax,54h[bx]

                div word ptr ds:0A8h[si]

                mov es:0dh[di],ax

                ;以上3句是存放人均收入

 

                add bx,4

                add si,2

                add di,16

                ;以上3句是为下一次循环时存放数据做准备

                ;3个寄存器递增的速度决定了所要存取的数据的位置的偏移地址

        loop s  ;跳到标号s处

mov ax,4c00h
int 21h

codesg ends

end start

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

相关推荐
朱包林5 分钟前
数据库服务-日志管理-备份与恢复-主从同步
linux·运维·服务器·数据库·mysql·云计算
原野风霜32412 分钟前
智能驾驶机器学习知识总结
机器学习·汽车电子
李法师_16 分钟前
lwIP MQTT 心跳 Bug 分析与修复
linux·c语言·stm32·单片机·lwip
神色自若1 小时前
AbpVnext 阿里云ssl证书多个生产环境自动更新
服务器·阿里云·ssl
超级大坏蛋20182 小时前
QT .pro文件的常见用法
java·linux·qt
眰恦ゞLYF3 小时前
服务器类型与TCP并发服务器构建(SELECT)
服务器·select·io多路复用
我好饿13 小时前
Linux入门教程 第十五章 Linux 系统调优工具
linux·运维·网络
万花丛中一抹绿3 小时前
服务器硬件电路设计之 SPI 问答(六):如何提升服务器硬件电路中的性能?如何强化稳定性?
服务器·spi·服务器硬件电路设计
萌虎爱分享4 小时前
Linux 防火墙 (firewalld) 管理完整指南
linux·运维·防火墙·firewalld
2401_888423094 小时前
网络编程-TCP的并发服务器构建
服务器·网络·tcp/ip