汇编程序设计

伪指令是一些看起来像指令,但实际上并不是CPU可以直接执行的指令。它们更像是一种特殊的命令,由汇编器(或编译器)处理。

伪指令的功能主要是帮助程序员更方便地编写和组织汇编程序。它们可以用来定义数据、布局程序、定义常量和符号,以及进行条件编译等操作。

举个例子,如果我们想在汇编程序中定义一些数据,比如一系列的数字或字符,我们可以使用伪指令来简化操作。通过使用伪指令,我们可以直接写下这些数字或字符,并告诉汇编器将它们保存在内存中的特定位置。伪指令会在编译时被汇编器解析,生成相应的机器码或控制信息。

另外,伪指令还可以帮助我们定义和使用宏(一种可以重复使用的代码片段)。宏可以简化我们的代码,提高代码的可读性和维护性。

总的来说,伪指令是汇编语言中一些特殊的指令,它们的目的是帮助程序员更轻松地组织和管理汇编程序,使得代码更加可读、易于编写和维护。

在汇编程序设计中,指令性语句和指示性语句是两个不同的概念:

  1. 指令性语句(Instruction Statements):这些语句用于执行实际的操作指令,例如进行算术运算、数据传输等。指令性语句是汇编程序中最常见的类型。例如,ADD、MOV、JMP等都属于指令性语句。这些指令直接影响程序的执行。
  2. 指示性语句(Directive Statements):这些语句用于指导汇编器进行一些操作,例如定义常量、分配内存空间、导入外部库等。指示性语句不会被直接转化为机器指令,而是在编译时由汇编器处理。例如,DB、DW、DS等就是常见的指示性语句。

简而言之,指令性语句主要用于执行操作指令,控制程序的运行;而指示性语句主要用于指导汇编器对程序进行处理和组织。两者在汇编程序设计中发挥不同的作用。

指令性语句和指示性语句在格式上有所不同:

  1. 指令性语句的格式:
    • 指令助记符(Instruction Mnemonic):指令的助记符表示具体的操作,如MOV、ADD、SUB等。
    • 操作数(Operands):指令所操作的数据或寄存器。操作数可以是立即数(Immediate)、寄存器(Register)、内存地址(Memory Address)等。操作数的数量和类型取决于具体的指令。
    • 示例:

MOV AX, BX ; 将BX的值移动到AX寄存器 ADD AX, 10 ; 将AX寄存器的值加上10 JMP LOOP ; 无条件跳转到LOOP标签处

  1. 指示性语句的格式:
    • 指令助记符(Directive Mnemonic):指示性语句的助记符表示具体的指导操作,如DB、DW、DS等。
    • 操作数(Operands):用于指定指示性语句所需的参数,如字节或字的值、内存空间的大小等。
    • 示例:

DB 65H ; 定义一个字节,值为65H DW 1234H ; 定义一个字,值为1234H DS 100 ; 分配100字节的内存空间

变量是内存单元的符号地址,本质上是一个存储器操作数

在8086汇编语言中,OFFSET和SEG都是取值操作符,用于获取一个标号或变量的偏移地址和段地址。

  • OFFSET

操作符:用于获取一个标号或变量的偏移地址,也就是其相对于所在段的内存地址。

    • 示例:

MOV AX, OFFSET label ; 获取label标号的偏移地址,并存储到AX寄存器中 MOV BX, OFFSET variable ; 获取variable变量的偏移地址,并存储到BX寄存器中

  • SEG

操作符:用于获取一个标号或变量所在的段地址。

    • 示例:

MOV AX, SEG label ; 获取label标号所在的段地址,并存储到AX寄存器中 MOV BX, SEG variable ; 获取variable变量所在的段地址,并存储到BX寄存器中

这些操作符在8086汇编语言中用于获取标号或变量的地址信息,以便进行内存访问。请注意,OFFSET和SEG操作符通常需要与其他指令结合使用,用于正确计算目标地址。

在汇编语言中,PTR是一个组合操作符,用于创建一个指向指定地址的指针。它将地址和显示模式结合起来,用于指示指针的类型和操作。PRT属于属性运算符。

PTR操作符的一般语法如下:

type PTR address

其中,type是指针的类型,可以是字节指针(BYTE)、字指针(WORD)或其他数据类型,address是指针指向的地址。

例如:

mov ax, WORD PTR [bx] ; 将从[bx]地址读取的字数据存储到ax寄存器中 mov al, BYTE PTR [si+2] ; 将从[si+2]地址读取的字节数据存储到al寄存器中

PTR操作符与操作数结合使用,用于声明或指示操作数的类型以及对应的地址。

需要注意的是,PTR操作符的具体语法和使用方式可能因不同的汇编语言而异。请参考特定汇编语言的文档和规范以了解准确的语法和操作。

如果写成MOV WORD PTR[BX],12H,那这个12H其实是0012H

在汇编语言中,DB、DW、DD、DQ和DT是用于定义数据的指示性语句(Directive),用于告诉汇编器如何分配内存和解释数据。

这些指示性语句的含义如下:

  1. DB

(Define Byte):用于定义一个字节(8位)大小的数据。

    • 示例:

myByte DB 65H ; 定义一个字节大小的数据,值为65H myChar DB 'A' ; 定义一个字节大小的数据,ASCII码为65,表示字符'A' myData DB 10, 20 ; 定义多个字节大小的数据,分别为10和20

  1. DW

(Define Word):用于定义一个字(16位)大小的数据。

    • 示例:

myWord DW 1234H ; 定义一个字大小的数据,值为1234H myValue DW 100 ; 定义一个字大小的数据,值为100 myData DW 10, 20 ; 定义多个字大小的数据,分别为10和20

  1. DD

(Define Doubleword):用于定义一个双字(32位)大小的数据。

    • 示例:

myDword DD 12345678H ; 定义一个双字大小的数据,值为12345678H myValue DD 100 ; 定义一个双字大小的数据,值为100 myData DD 10, 20 ; 定义多个双字大小的数据,分别为10和20

  1. DQ

(Define Quadword):用于定义一个四字(64位)大小的数据。

    • 示例:

myQword DQ 123456789ABCDEF0H ; 定义一个四字大小的数据,值为123456789ABCDEF0H myValue DQ 100 ; 定义一个四字大小的数据,值为100 myData DQ 10, 20 ; 定义多个四字大小的数据,分别为10和20

  1. DT

(Define Ten Bytes):用于定义一个十个字节(80位)大小的数据。

    • 示例:

myTenBytes DT 12345678901234567890H ; 定义一个十个字节大小的数据 myData DT 10, 'A', 3.14 ; 定义多个十个字节大小的数据

这些指示性语句可用于在程序中分配内存,并将指定的值存储到相应的内存位置中。具体的语法和用法可能因汇编语言而异,所以请参考特定汇编语言的文档和规范以了解准确的语法和用法。

在汇编语言中,定义一个名为M1的变量和定义一个以M1为首地址的变量有些微妙的差别。

  1. 定义一个名为M1的变量(Define a variable named M1):

这表示声明一个变量并分配内存,将其命名为M1。该变量的地址由汇编器分配,并可在程序中使用该名称来引用变量。

示例:

M1 DB 55H ; 定义一个字节大小的变量,名称为M1,初始值为55H M2 DW 1234H ; 定义一个字大小的变量,名称为M2,初始值为1234H

  1. 定义一个以M1为首地址的变量(Define a variable starting at M1):

这是一种相对的方式,表示在现有内存位置的基础上定义一个变量。变量名可以是任意的,而M1则表示变量的起始位置(地址)。

示例:

M1 DB 10, 20, 30 ; 在地址M1处定义一个字节大小的连续数据,值分别为10、20和30 M2 DW M1 ; 在地址M2处定义一个字大小的变量,并将M1作为其起始地址

总结来说,定义一个名为M1的变量是显式地声明一个变量并使用特定的名称来引用它。而定义一个以M1为首地址的变量则是隐式地在现有内存位置上定义了一个变量,并可通过相对地址偏移来访问它。

需要注意的是,在不同的汇编语言中,语法和语义可能会有所不同。因此,请根据所使用的具体汇编语言的要求和规则来正确定义和使用变量。

举个例子,第一句中 DATA1被定义为字节类型的变量,那么他后面的每一个操作数都是一个字节,DATA2被定义成字类型的变量,所以后面的11H其实是0011H,DATA3被定义成双字类型变量,泽塔后面的每一个操作数都是占四个字节,第一个操作数应该是0000 0022H

这里省略了H,因为在汇编程序中我们看到的内存中编号默认都是十六进制的,DATA1,DATA2,DATA3是连续存放的,因此如果我们知道了DATA1的地址,就知道了后面两个变量的地址

虽然一个字符串可以很长,但是它是由一个个字符组成的,我们规定定义字符串必须用DB,且用单引号引起来,实际上在内存中存的是他们的ASCII码值

其中n是重复的次数 ()里面是重复的内容

这句话的意思是定义了10个以M1为首地址(或者说变量名为M1)的字节类型变量,每个变量的内容都是0

重复操作符经常用来声明一个数据区。也就是说我们并不关心这片区域的内容是什么,我只想要有这么一块区域,以后可以用来存放东西比如运算结果等等。

第一条语句表示定义了一个名为MEM1的字节类型变量,第一个操作数是34H,第二个操作数是A的ASCII码值,第三个操作数是一个字节的随机值,第二条语句中省略了变量的名字,但是创建了20个字类型的变量,每一个单元的内容都是随机数,表示预留了40个字节单元。

第一句表示定义了一个变量名为M1的字节类型变量,系统会分配给他一块内存空间,这块空间后面字母以及空格的ASCII码值,具体系统给的哪一块空间我们不知道,但是我们可以把这块空间的首地址说是M1。也就是说M1为首地址的内存区域内连续存放着How are you?的ASCII码值。

第二句表示定义了一个变量名为M2的变量,也即获得了系统分配的一块首地址为M2的内存空间,从M2地址开始,向后面占用3个字(6个字节)的内存单元,每一个字单元存放的是0011H,然后紧接着存放3344H到下一个字单元。

第三句省略了变量名,在某一个位置获得了系统分配的空间,里面放了四个字节的随机值,即占用了四个字节单元,每一个单元里面放了一个随机值。

第四句表示定义了一个变量名为M3的变量,也即获得了系统分配的一块首地址为M3的内存空间,这块空间有多大,得看后面,显然是从M3地址处连续三次,每一次都放的是22H 11H和随机值,因此一共占用了9个字节的内存单元。他们在内存中的分配如图

符号定义伪指令EQU的作用类似于C语言中的宏定义,与数据定义伪指令的本质区别就是EQU说明的表达式不占用内存空间。他只是在我们编程序的时候用了一下,就像是一个符号。因此他叫符号定义伪指令。

在8086汇编语言中,SEGMENT 是一条伪指令,用于定义一个代码或数据段。它一般与 ENDS 指令配套使用来表示段的起始和结束。

具体的语法如下:

segment_name SEGMENT ; 代码或数据定义 segment_name ENDS

其中,segment_name 是用来标识段的名称,可以自定义,在 SEGMENT 和 ENDS 之间可以定义代码或数据。

以下是一个示例,展示了如何使用 SEGMENT 指令定义一个数据段:

DATA SEGMENT myByte DB 55H ; 定义一个字节大小的变量 myWord DW 1234H ; 定义一个字大小的变量 DATA ENDS

在上面的示例中,DATA 是段的名称,SEGMENT 表示段的开始,ENDS 表示段的结束。在 DATA 段中,可以定义不同类型的数据,例如 myByte 和 myWord。

请注意,这种使用 SEGMENT 指令的方法是8086汇编语言中的特定语法。不同的汇编语言可能有不同的语法和指令。如果你使用的是其他种类的汇编语言,请参考相应的语法和规范。对于8086 CPU上的汇编语言,你可以使用 SEGMENT 指令来定义逻辑段。

当我们在8086汇编语言中定义逻辑段时,我们使用 SEGMENT 指令来创建一个代码或数据段。但是,只是定义了逻辑段还不够,我们还需要告诉计算机如何将这些逻辑段与段寄存器关联起来。

在这里,ASSUME 指令就像是一个约定,它告诉计算机哪个段寄存器应该指向哪个逻辑段。通过使用 ASSUME 指令,我们可以建立段寄存器和逻辑段之间的关联关系。

为什么需要这个关联呢?因为在8086处理器中,当我们访问一个段的数据时,需要使用段寄存器来指定要访问的是哪个段。如果我们没有明确地告诉计算机哪个段寄存器应该指向哪个逻辑段,它就无法知道我们想要访问的是哪个数据。

因此,通过使用 ASSUME 指令,我们在程序中建立了一种约定,告诉计算机哪个段寄存器(比如 CS、DS、SS 等)应该指向哪个逻辑段。这样,当我们在程序中访问数据时,计算机就知道使用哪个逻辑段了。

总结起来,ASSUME 指令的作用我们在用SEGMENT和END搭配定义了一个个逻辑段之后,这些逻辑段在哪里是我们自己知道的,但是编译器并不知道,因此我们需要用ASSUME让编译器知道这些段基址对应的段寄存器是哪个。

使用SEGMENT和END的搭配分别声明了四个逻辑段,现在DSEG,ESEG等等都是我们定义的逻辑段的段基址,我们习惯上把代码段放在最后声明,然后用ASSUME把我们定义的逻辑段和相应的段寄存器联系起来,也就是说告诉编译器每个段对应的段寄存器是哪个,但是ASSUME不会把段基址装入到段寄存器里面,也就是说现在段寄存器里面是空的,而我们以后如果想要访问DSEG这个逻辑段的内容,是通过段寄存器中的内容来访问的,段寄存器里面存放的是基地址,而DSEG恰好是基地址,那么用MOV指令把DSEG送入AX,然后通过AX再送给DS即可。值得注意的是,代码段寄存器并不需要人为初始化,这是由编译器自动完成的。我们写的代码也都是在段寄存器初始化之后。

通常来说我们并不需要每次都声明四个类型的逻辑段,但是由于我们要写代码,因此必须声明代码段,我们操作的数据通常在内存中,因此通常要声明数据段,如果有串操作,则需要声明附加段,因为规定字符串指令的目标操作数必须位于附加段,如果涉及到了PUSH,POP等指令要声明堆栈段。如果是一个很短的代码,操作数全都是立即数或寄存器,那只需要声明代码段。

在8086体系结构下,有一些指令在使用时必须先声明堆栈段,以确保程序的正确执行和数据的正确存取。以下是几个必须先声明堆栈段的指令:

  1. CALL指令:用于函数调用,将当前的指令位置(返回地址)压入堆栈中,并跳转到指定的函数。在使用CALL指令之前,需要确保堆栈段已经正确地声明并分配了足够的内存。
  2. RET指令:用于从函数返回,将返回地址从堆栈中弹出,并跳转回调用处。RET指令需要正确的堆栈段设置,以确保能够正确地读取返回地址。
  3. INT指令:用于触发中断,需要将中断处理程序的入口地址或中断向量号压入堆栈中。在使用INT指令之前,需要先声明并设置好堆栈段。
  4. PUSH和POP指令:用于将数据压入堆栈或从堆栈中弹出数据。这些指令需要确保堆栈段被正确设置,并且堆栈指针(SP)指向有效的堆栈区域。

这些指令在执行时会直接或间接地使用堆栈段,所以堆栈段的正确声明和设置对于程序的正确执行非常重要。在程序开始处,需要使用STACK指令来声明堆栈段的大小,并确保堆栈指针(SP)正确初始化。

在8086体系结构中,过程定义伪指令是用于定义过程(也称为子程序)的指令。过程是一段被命名的可重复使用的代码块,它接收一些输入参数,执行特定的任务,并返回一个或多个结果。

在8086汇编语言中,有几个伪指令用于定义过程,其中最常用的是PROC和ENDP指令。下面是过程定义的基本语法:

名称 PROC [参数列表] ; 过程体 名称 ENDP

在这个语法中,名称是过程的标识符,用于在程序的其他部分调用过程。参数列表是可选的,用于定义传递给过程的参数。

过程体是一系列的汇编指令,用于实现过程的功能。在过程体中,你可以使用寄存器、内存操作和其他指令来完成特定的任务。

当过程执行完毕时,使用ENDP指令来标记过程的结束。

下面是一个示例,展示了如何定义一个简单的过程来计算两个数的和,并将结果存储在一个寄存器中:

; 过程定义 ADD_TWO_NUMBERS PROC MOV AX, [BP+4] ; 将第一个参数存储在AX寄存器中 ADD AX, [BP+6] ; 将第二个参数与AX寄存器中的值相加 RET ; 返回结果 ADD_TWO_NUMBERS ENDP ; 过程调用 MOV BX, 10 ; 第一个参数 MOV CX, 20 ; 第二个参数 CALL ADD_TWO_NUMBERS ; 调用过程

以上示例演示了如何定义一个名为ADD_TWO_NUMBERS的过程,它接受两个参数,并将它们相加的结果存储在AX寄存器中。然后,通过使用CALL指令,我们可以在程序中的其他位置调用这个过程并传递参数。

在定义子程序时,将原来寄存器的内容保存起来是出于以下几个原因:

  1. 避免数据丢失:子程序可能需要使用一些通用寄存器来执行特定的任务。如果在子程序内部不保存寄存器的内容,那么执行子程序时,原来寄存器中的值会被覆盖,导致数据丢失。
  2. 保持上下文一致:在调用子程序之前,调用者可能已经使用了一些寄存器存储了重要的数据或临时结果。为了保持调用者的上下文一致,在调用子程序之前,将这些寄存器的内容保存起来是必要的。当子程序执行完毕后,使用之前保存的内容恢复寄存器的值,以确保调用者能够继续使用这些寄存器中的数据。
  3. 独立性和可重入性:子程序通常是可重复使用的,可以在程序的不同部分多次调用。为了确保子程序的独立性和可重入性,应该保证子程序的执行不会对外部环境造成干扰。保存寄存器的内容是一种常见的做法,以确保子程序在执行期间不会干扰调用者或其他部分的代码。

一般做法是用PUSH指令把寄存器中的内容压入堆栈,最后用POP指令取出。

在8086汇编语言中,宏命令伪指令用于定义和使用宏,它们又被称为宏定义和宏调用指令。宏是一种将一系列指令片段组合在一起并按需使用的机制,它可以在程序中多次调用,提供更高级的代码重用和简化。

下面介绍两个常用的宏命令伪指令:

  1. MACRO

:MACRO指令用于定义宏。宏定义类似于一个代码模板,以关键字MACRO开始,后面跟着宏的名称和参数列表。在定义宏时,可以使用一些特殊符号(例如&)来表示参数,以在宏调用时进行替换。

以下是宏定义的基本语法:

宏名称 MACRO 参数列表 ; 宏体 ENDM

在宏体中,可以编写一系列的汇编指令,并使用参数替换来定制代码。在程序中的任何位置,可以通过宏调用来使用该宏,并传递相应的参数。在编译时,宏调用会被替换为宏定义中的代码片段。

  1. INVOKE

:INVOKE指令用于调用一个已定义的宏,并传递参数。INVOKE指令具有宏名称和可选的参数列表,用于在编译时展开宏并将参数替换为实际的值。

以下是INVOKE指令的基本语法:

INVOKE 宏名称, 参数列表

根据宏定义中的参数列表,使用逗号分隔实际的参数。这些参数将替换宏定义中相应的参数,并展开为一系列的汇编指令。

使用宏命令伪指令可以有效地提高代码的重用性和可读性。通过定义宏,可以将常用的代码片段封装起来,并在需要的地方多次调用,减少代码冗余,并使程序更加模块化。

宏和子程序的本质区别是:子程序是可以独立存在的,就像C语言的库函数,谁用谁就可以调用,而宏是不能独立存在的,他是源程序的一部分。

在汇编语言中,有一些伪指令可以用于调整偏移量,其中两个常用的指令是ORG和EQU。

ORG(Origin)指令:ORG指令用于设置程序的起始地址或偏移量。它可以指示汇编器生成的机器代码存放的位置。ORG指令通常用于调整程序中的标签、标记和数据的偏移量。它的语法如下:

ORG 偏移

偏移量可以是一个立即数、标号或一个表达式,用于指定程序的当前位置。在汇编器遇到ORG指令时,它会将生成的机器代码放置在指定的偏移量位置。ORG指令通常用于修改程序的加载地址或指定特定的存储位置。

系统功能调用(其实就是调用BIOS或DOS)

着重介绍调用DOS即DOS中断

DOS(Disk Operating System)是指磁盘操作系统,它是早期个人计算机上使用的一种操作系统。DOS最初由微软(Microsoft)开发,它提供了一系列命令和功能,用于管理计算机硬件和软件资源。

功能:DOS提供了基本的文件管理、目录结构、输入输出以及系统配置的功能。它允许用户使用命令行界面进行文件操作、运行程序、设置系统参数等

DOS软中断(DOS Software Interrupt)是一种在DOS操作系统中使用的机制,用于向DOS内核请求服务和执行系统功能。通过触发软中断,程序可以向DOS发出请求,以进行文件操作、系统调用、内存管理等操作。

下面是一些关于DOS软中断的重要信息:

  1. 中断向量表:DOS维护了一个中断向量表(Interrupt Vector Table),其中每个中断类型都对应一个中断向量。每个中断向量由一个4字节的地址组成,指向对应的中断服务程序。
  2. 触发软中断:要触发DOS软中断,程序需要使用INT指令,后跟中断号。INT指令会导致CPU跳转到相应中断向量指向的中断服务程序。
  3. 中断服务程序:DOS提供了一系列的中断服务程序,用于处理不同类型的请求。这些中断服务程序由DOS内核提供,并负责执行相应的功能。
  4. 在DOS中,最常用的软中断是INT 21h,它是DOS功能调用中断,用于执行各种系统调用和文件操作。INT 21h中断是由DOS内核提供的,通过指定不同的功能号和传递适当的参数来请求特定的操作。以下是一些INT 21h中的常用功能号:
  • 01h:键盘输入操作
  • 02h:屏幕字符输出
  • 09h:在屏幕上显示字符串
  • 25h:创建文件
  • 26h:删除文件
  • 2Ah:获取系统时间
  • 4Ch:程序结束
  1. 参数传递与返回值:在触发DOS软中断时,程序可以将参数传递给中断服务程序,以便请求特定的操作。中断服务程序执行完成后,可能会返回一些结果值,供程序继续使用。

DOS软中断是程序与DOS操作系统交互的重要途径,它允许程序通过请求特定的中断服务程序来访问DOS提供的各种功能和服务。通过使用合适的中断号和参数,程序可以实现文件访问、IO操作、内存管理等操作,从而完成各种任务。

INT指令(Interrupt)是8086体系结构中的一条指令,用于触发软中断(软件中断)。它允许程序主动地请求操作系统或其他软件服务的执行。

以下是关于INT指令的一些重要信息:

  1. 用途:INT指令用于将控制权转移到中断向量表中指定的中断服务程序。中断服务程序是在操作系统或其他软件中预定义的,用于执行特定的功能或服务。
  2. 格式:INT指令的语法为"INT n",其中n代表一个中断向量号(介于0和255之间)。中断向量号对应着中断向量表中的一个位置,该表保存了各个中断服务程序的入口地址。
  3. 中断向量表:中断向量表是一个包含256个4字节(32位)项的数据结构,每个项存储一个中断服务程序的入口地址。中断向量号在中断指令中被用作索引,从而确定要执行的中断服务程序。
  4. 中断类型:不同的中断向量号对应着不同的中断类型和功能。例如,INT 10h用于调用显示服务,INT 21h用于调用DOS功能等。这些中断服务程序可由操作系统提供,也可由其他软件或设备驱动程序提供。
  5. 中断处理过程:当执行INT指令时,8086处理器会执行一系列操作来处理中断。通常,处理器会将当前的程序状态保存在堆栈中,然后跳转到指定的中断服务程序入口地址执行相应的任务。完成后,处理器会从堆栈中恢复先前的程序状态,并继续执行中断指令之后的指令。

通过使用INT指令,程序可以主动地请求特定功能的执行,例如从操作系统获取服务、处理硬件中断等。中断的使用可以提高系统的灵活性和可扩展性,使程序能够与外部环境进行交互。

前两行执行完之后屏幕上会出现一个光标,表示能输入一个字符,输入完成后继续执行下去会和Y比较,如果是Y就跳到YES那里,如果是N就跳到NO那里。

N1是需要我们自己输入的,N2是根据我们输入的字符串系统自动数出来填进去的,且N2不包括回车。值得注意的是由于最后字符串输入完成之后一定要按一下回车,因此如果我们输入的N1是10,那我们实际上最多输入9个字符。缓冲区最大是255

在前面学习中我们说只有四个寄存器BX BP SI DI,他们被称为间址寄存器,但是这里DOS软中断的时候用的是DX存放字符串在内存中的地址。这是规定。

比如上面这个例子,在定义缓冲区的时候要按照规定的格式来,首先20就是N1,表示缓冲区能容纳的最大字节数是20,?是实际输入的字符个数N2,N2不管填几理论上来说都无所谓,因为这是系统根据我们输入的字符个数自动更新的,但是一定要把这个位置留出来。再后面就是获得了系统分配的20个字节空间。“20”:表示要进行重复的次数,这里是重复20次。“DUP”:是英文单词"Duplicate"的缩写,表示重复的意思。“(?)”:是表示要进行重复的数据的占位符。然后要把字符串在内存中的地址送给DX,功能号送给AH,然后INT21H就能输入字符串了。实际上我们只能输入19个字符,最后一个是回车。

对于前面的单字符输入和字符串输入,他们的入口就是键盘,因此我们需要给他一个出口,其实就是缓冲区,而对于单字符显示输出,他的出口就是屏幕,因此我们需要给他一个入口,汇编语言规定入口是DL,因为对于单字符来说他就是一个8位的ASCII码值。之所以叫单字符显示输出,是因为在用DOS软中断的字符显示输出时,他会把DL里面的内容解释称ASCII码值,因此上面执行结果是A

如果没有0DH,0AH,在执行完这一系列操作之后光标会在冒号上面闪烁,假如我们再输入一些东西,就会把冒号覆盖掉,这时候我们可以输入0DH也就是回车的ASCII码,这样我们执行完这一系列操作之后光标就在冒号后面闪烁,我们再输入别的东西就不会吧冒号覆盖掉了,如果我们希望下一次输入的内容换行,那么我们可以使用0AH也就是换行符的ASCII码值,此时输入完成之后光标会在下一行闪烁。

汇编程序设计例题

用DB定义了一个变量名为NAMES的字节类型变量,后面的TOM..在内存中分别用他们的ASCII码值表示,20也占一个字节,注意这里都是16进制数,省略了H

首先定义了一个数据段,然后在数据段定义了一个名字是A的字节类型变量,获得系统分配的内存中依次存放123ABC的ASCLL码值,然后DATA ENDS,紧接着有定义了代码段,用ASSUME把定义的逻辑段和他们对应的寄存器联系起来,然后是寄存器的初始化。在阅读代码的时候后我们发现关键的一句是INT 21H,"INT 21H"指令用于调用DOS提供的功能和服务,其中"21H"表示调用的功能号。通过设置寄存器AH来指定具体的功能号,可以调用不同的DOS功能。然后就要往上面找看看AH里面是什么,发现AH里面是2,表示调用的是单字符显示功能,由于单字符显示的是DL里面的内容,因此我们现在应该分析DL里面的内容是什么,MOV AL,[BX]表示把BX指向的内容送到了AL里面,XCHG AL,DL表示交换AL和DL的内容,因此DL里面存放的就是BX指向的内存空间中的内容,因为前面LEA BX,A是把A的地址拿出放到BX里,注意A并不是内容,而是一个变量或者说是符号地址,他里面存放的内容是123ABC,因此第一次执行完INT 21H这一句会把字符1打印在屏幕上,LOOP会把CX自动减一并再次从LP执行,直到CX为0,因此最终执行结果是123ABC依次出现,注意不是同时出现,因为这里是单字符显示。一次只能显示一个。

这个代码中最主要的一句就是红圈圈起来的这句,表示[BX+SI+2]采用基址变址相对寻址的方式,总而言之[]里面是一个地址,DWORD PTR[]表示把这个地址指向了一个双字,通过[]找到了这个双字并把它解释成一个地址,由于这个地址是双字也就是32位的,因此是段间转移,目标地址在BX+SI+2起始的四个单元中,BX是TABLE的偏移地址,而TABLE是我们定义的逻辑段的名字,默认偏移地址是0,SI是06H,因此目标地址的起始地址是从TABLE开始往后数8个字节单元,所以JMP之后的一个双字是2300H,1200H,这个双字被解释称一个地址,高地址存放在CS中,低地址存放在IP中。

"DATA SEGMENT"是汇编语言中一个用于定义数据段的伪指令。数据段是一块内存区域,用来存储程序中定义的数据。在8086汇编语言中,程序的内存结构通常分为代码段(CODE SEGMENT)和数据段(DATA SEGMENT)。代码段用于存放可执行的指令,数据段用于存放程序所需的数据。

使用DATA SEGMENT指令可以定义一个数据段,并将数据段的起始地址(SEGMENT名称)赋值给段寄存器DS(数据段寄存器)。

数据段的定义通常包括以下步骤:

  1. 定义DATA SEGMENT:使用DATA SEGMENT指令开始定义数据段。
  2. 声明数据:在DATA SEGMENT中,可以使用其他伪指令如DB(定义字节)、DW(定义字)、DD(定义双字)等来声明变量。
  3. 结束数据段:使用ENDS指令结束数据段的定义。

DATA SEGMENT var1 DB 10 ; 定义一个字节型变量var1,初始值为10 var2 DW 2000 ; 定义一个字型变量var2,初始值为2000 array DB 1, 2, 3, 4 ; 定义一个字节数组array,初始值为1, 2, 3, 4 DATA ENDS

在这个示例中,使用DATA SEGMENT指令定义了一个数据段,并在其中声明了三个变量:一个字节变量var1、一个字变量var2和一个字节数组array。这些变量将存储在数据段中。

同时,当定义了数据段后,可以使用MOV指令将数据加载到寄存器或内存中进行操作。

首先用DATA SEGMENT定义了一个代码段,然后可以声明了字节类型的变量STR1和STR2,双字类型的变量COUNT和一个字节类型的变量FLAG,然后DATA ENDS,表示结束本数据段的定义,CODE SEGMENT 定义了一个代码段,然后ASSUME把我们刚才定义的代码段和数据段与对应的寄存器联系起来,这样,程序就可以方便地通过段寄存器来访问对应的段。注意本例中DATA同时和ES DS联系起来,因为附加段和数据段是可以重叠的,紧接着START后面的三条MOV指令表示寄存器的初始化,因为刚才ASSUME仅仅是把我们定义的逻辑段与对应的寄存器联系起来了,但是寄存器里面还没放东西,然后代码中关键的一句是REPE CMPSB,"REPE"是8086汇编语言中的指令前缀,表示"repeat while equal",也可以表示为"REPZ",它用于在满足一定条件时重复执行指令。

REPE指令前缀通常与字符串操作指令一起使用,比如CMPSB、SCASB等。它的作用是重复执行带有指定条件且可重复的字符串操作指令,直到不满足条件为止。

当重复执行的条件是相等(ZF = 1)时,使用REPE/REPZ前缀,而当重复执行的条件是不等(ZF = 0)时,使用REPNE/REPNZ前缀。

举个例子,如果使用CMPSB指令(比较字符串字节)配合REPE前缀,那么会重复执行CMPSB指令直到不再满足两个字节相等的条件。

需要注意的是,REPE指令前缀与标志位ZF(零标志位)相关。执行指令时,ZF的值会在每次比较后根据结果进行更新。

"CMPSB"是8086汇编语言中的一条指令。它是"compare string byte"的缩写,用于比较两个字符串中的字节数据。

指令格式为:CMPSB

这条指令的作用是将DS:SI指向的内存字节与ES:DI指向的内存字节进行比较,并根据比较结果修改FLAGS寄存器。具体的比较规则如下:

  • 如果两个字节相等,则ZF(零标志位)会被设置为1,表示相等;
  • 如果两个字节不相等,则ZF会被设置为0,表示不相等,并根据大小对应修改其他标志位。

注意,执行CMPSB指令后,SI和DI寄存器的值会根据DF(方向标志位)的设置进行自动递增或递减。

需要注意的是,CMPSB指令用于比较字节数据,如果要比较字或者其他数据类型,可以使用不同的指令。

CMPSB是把DS:SI和ES:SI指向的内容进行比较,现在SI指向了STR1首元素,DI指向了STR2首元素,因此第一次比较的是H和H,二者相同,注意其实不管相同还是不同,都会使SI和DI+1并且CX-1,前面有前缀REPE表示相同则重复,直到比较的东西不同或者CX=0为止。因此直到比较R和O才停下来,H是STR1的零地址,因此直到比较R和O,SI应该是8,但是比较完之后不管相等还是不等SI和DI会自动+1,因此SI最终是9,因为STR2的首元素在12地址处,因此O在20地址处,比较完之后+1,故DI应该是21即0015H,因为比较了八次,每次比较CX都-1而且第八次比较完之后CX也-1,因此CX=12-9=3.因为在REPE CMPSB停下来之后两个内容不相同,所以ZF=0,因为JZ是在ZF=1的条件下才会跳转到NEXT1,因此不会跳过去,而是继续执行下一条MOV指令,把00H送到BX指向的字节单元中,而BX指向的字节单元是FLAG,因此FLAG=0。

首先DATA SEGMENT表示定义了一个数据段,然后紧接着有创建了一个名为SUM的字节类型变量,并获得了系统分配的8个字节单元内存,里面放的全是0,然后DATA ENDS表示数据段定义的结束,CODE SEGMENT表示定义代码段,然后定义完代码段之后固定用ASSUME把我们定义的逻辑段与他们相应的寄存联系起来,然后是寄存器的初始化,接着看比较重要的一条指令是ROR AL,1

ROR是汇编语言中的一个指令,代表"Rotate Right",即向右循环移位。它将指定的数据或寄存器中的二进制位向右循环移动,并将移出的位放置到最左边。

ROR指令的语法通常如下:

ROR destination, count

其中,destination表示要进行循环移位的目的地,可以是一个寄存器或者内存位置;count表示循环移位的次数,可以是立即数(常量)或者寄存器。

循环移位会将目标数据的每个二进制位向右移动指定的位数,同时将最右边的位循环移动到最左边。移出的位会被保存到进位标志(Carry Flag)中,可以在之后的指令中进行判断和使用。

举个例子,假设AL寄存器中存放的是十进制数85(二进制表示为01010101)。如果我们执行以下ROR指令:

ROR AL, 1

之后,AL的值将变为170(二进制表示为10101010),相当于将二进制数向右循环移位一次,最右边的位移动到了最左边。

回到本题,在第一次执行完ROR AL,1之后,即把AL里面存放的内容45H右移一个二进制位同时把移出去的那一位放在CF里面的同时把它放到第一位。现在CF里面放的是1,JNC表示CF为零则跳转,因此这里不会跳转,而是把OFFH放到了BX指向的字节单元也就是SUM0当中,因为CF里面的内容是1还是0,与当前AL中二进制序列的最后一位有关。最后一位是1,执行ROR之后CF就是1,CF是0,执行ROR之后CF就是0。而如果是0,执行完ROR之后就跳转到了NEXT1处并把0送给BX当前指向的字节单元,因此最终SUM0~SUM8分别是FF,0,FF,0,0,0,FF,0。不管跳转与否,每次都会执行NEXT2,每次都会把BX+1,因此执行了八次最终BX=8。而AL本身是八位二进制码,他右旋八次相当于没变,因此最终AL仍然是45H。

首先DSEG SEGMENT 定义了一个数据段,然后在里面创建了一个名为MESS的字节类型变量,现在MESS就是一块内存空间的首地址,他指向的字节单元里面依次存放着Please等等这样一个字符串,然后还放上了回车符和换行符让形式上看起来更美观,最后字符串用$表示结束。然后是字符缓冲区的定义,N1=20,然后又定义了一个字节类型的变量DATA。DSEG ENDS表示数据段定义的结束。紧接着又是定义代码段并用ASSUME把我们刚才定义的逻辑段与他们对应的寄存器联系起来,我们说涉及到串操作的指令,必须把目标操作数放在附加段,而我们刚才并没有定义附加段,但是我们知道数据段和附加段是可以重叠的,为了编程的方便,我们直接就用ASSUME把ES也和DSEG联系起来即可。然后START后面三行就是寄存器的初始化,接下来三行是把以MESS为首地址的字符串打印在屏幕上。然后红色的三行是让我们在缓冲区BUFF中输入一串字符,这里调用的是DOS 21H功能包的10号功能,由于字符缓冲区的特殊性,我们输入的字符串实际上是从BUFF+2的地方开始的,而BUFF处存的是N1,BUFF+1处存的是N2,也就是我们实际输入的字符个数。因为我们后面要用MOVSB指令将ES:DI指向的字节数据复制到DS:SI指向的位置,并根据方向标志位DF的状态调整SI和DI的值。REP指令通常与字符串操作指令结合使用,例如MOVSB、MOVSW等。它的作用是让指定的字符串操作指令重复执行指定的次数,直到指定的CX减为零为止。因此我们就要先把我们实际输入的字符个数先传给CL,在这之前需要吧CX清零即异或自己。CLD 指令的作用是将 DF 清零,即将方向标志位设置为0。这样,在执行字符串操作指令(比如 MOVSB, MOVSW, SCASB, CMPSB等)时,操作方向会是从低地址到高地址。最后应该返回操作系统,即MOV AH,4CH INT 21H。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值