大理寺少卿 发表于 2024-12-18 21:44:20

8086汇编(16位汇编)学习笔记05.asm基础语法和串操作

### asm基础语法

#### 1. 环境配置

!(./notesimg/1652352700101-09361716-8c6e-4809-af41-038d595a3a63.png)

##### xp环境配置

1.拷贝masm615到指定目录

2.将masm615目录添加进环境变量

3.在cmd中输入ml,可以识别即配置成功

!(./notesimg/1652352568514-66e7f17c-0121-4b97-826f-6f5bfe3491d8.png)

##### dosbox环境配置

1.拷贝masm611到指定目录

2.将masm611所在目录添挂载进dosbox

3.将masm611目录在dosbox中添加进环境变量

4.在cmd中输入ml,可以识别即配置成功

!(./notesimg/1652352599430-5b45d7c9-b5e5-420c-89e9-fc052be010e4.png)

##### window10 环境配置

masm615是32位程序的(可以在xp系统上用)       因此要用   masm611

1.把文件复制到 dosbox-x 挂载的文件目录下

!(./notesimg/1652353256564-a280cc73-9c93-4de4-ba38-a7321339f646.png)!(./notesimg/1652353279490-3977a90e-f7e8-408a-b8e2-b45ca6ccb43e.png)

!(./notesimg/1652353359366-83225d7a-226c-41c1-9f8d-c749fcda3466.png)

这样比较麻烦,所以可以把路径放入环境变量,在放入配置文件   set path=%path%;c:\masm611\

!(./notesimg/1652354078885-6dec93e3-83c2-42ea-9452-afcd85dad842.png)

!(./notesimg/1652354233317-d3921471-4e78-4e95-a468-1fdf5e8da22c.png)

#### 2. 入口和段

##### 入口

```
CODE segment                                             

START:                              

        mov ax, ax                              

CODE ends   

//表示标号START的第一行代码就是程序起点                                                   

end START
```

##### 段

1. 一个程序必须至少有一个段
2. 一个程序中可以定义多个段
3. 段不能嵌套
4. 段可以重名,重名的段会被编译到同一块内存中
5. 一般代码和数据是放在不同段内,一般有个代码段,一个数据段,一个栈段

格式:

```
段名 segment
```


```
ends 段名
```


```
TEST0 segment
    mov cx, cx
TEST0 ends

CODE segment
    mov dx, dx
CODE ends

CODE segment
    mov bx, bx
CODE ends


CODE segment

START:
    mov ax, ax
CODE ends

end START
```

#### 注释

```
汇编中使用分号( ; )来标注行注释,只有行注释,没有块注释
```


```
;这里是注释

mov ax, bx ;这里是注释
```

#### 常量

##### 整数

1.整数可以支持多个进制

2.数值必须以数字开头,如果**非数字,前面必须加0(如abcH必须写成0abcH)**

3.负数前面可以加减号(-)

!(./notesimg/1652361839486-df11d8c7-70f8-418f-88cd-3bc4f5db8ac1.png)

!(./notesimg/1652362772799-f47a90f5-6691-4330-8a6b-9bb6b191ac7e.png)

##### 字符

1. 字符可以用单引号(‘)或双引号(””)

mov byte ptr , '$'

!(./notesimg/1652363179202-4b504928-d831-4bde-a478-85d90ebf2dc0.png)

#### 5. 变量

##### 整形

1.整数可以支持多个类型

2.整数可以有多个初值,未初始化的值用问号(?)表示

3.变量一般定义在一个单独的段中

```cpp
变量名    类型    初始值
val      dd      5566h
```

!(./notesimg/1652363315749-5d3c7d4c-fa57-48b8-b2ec-03b9488f4cff.png)

!(./notesimg/1652363726696-19e372ff-f41a-48f4-998d-bf3220f29a3f.png)

!(./notesimg/1652364126311-745ada41-288d-43b4-9f5d-c72f65c6f364.png)

!(./notesimg/1652364515761-46b61fb5-c94d-4fe3-95c6-b79f552fed9d.png)

!(./notesimg/1652364845593-53ee2d03-34be-4559-864a-ed3571348b4d.png)

!(./notesimg/1652365970409-cd60d017-934c-4719-8703-ad31ba61cbde.png)

!(./notesimg/1652366491493-6ea06b17-0e1a-4813-97de-20866518b8c3.png)

##### 字符串

1.字符串都可以用单引号(‘)或双引号(””),单引号和双引号作用一样

2.字符串一般以美元符$结尾

g_sz db "hello world$"; 16位汇编中以美元符结尾

!(./notesimg/1652366831530-ab1bd1ae-3a29-4757-8258-3087adadc474.png)

##### 数组

```
;这里是数据
data segment    ;变量不允许重名
   g_arydw12,13,14,15,16,17;长度等于你定义的个数
   g_ary1 dw16 dup(55h)      ;表示定义了一个长度16,初始值都是55的数组
   g_ary2 dw66h,4 dup(8888h),9999h,3 dup(7777h)

   g_bt db 12H   ;一个字节
   g_wdw ?       ;未初始化
   g_w1dw 1213h   ;双字
   g_w2 dw ?       ;未初始化
   g_dd1 dd 1234h   ;四个字节
   g_sz db "hello world $" ;汇编不会自动帮你加'\0'

data ends

;这里是代码
codesegment   ;段开始

START:;标号
    assume ds:data   ;指定data段 作为 ds 段
    mov ax,data      ;先把data的偏移值给ax
    mov ds,ax      ;ax再把data的偏移值给ds
                     ;不直接给是因为没有立即数到段寄存器

   mov al,g_bt   ;将变量 g_bt 的值给al
   mov ax,g_w1   ;将变量 g_w1 的值给bx
   mov g_w,1234h   ;给未初数化变量 g_w 赋值
   mov g_w2,bx   ;将bx的值,赋值给 g_w2
   

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

格式:

```
名字 类型 值1[,值2][,值2][,值2][,值2]
```


```
名字 类型 数量 dup (初值)[,数量 dup (初值)][,值]
```


示例

```
g_db   db 78h, 96h, 43h;后面跟初始化的值
```


```
g_arydb 256 dup(0), 128 dup(11h);重复256个0,再跟重复128个1
```


!(./notesimg/1652367459174-bfcc3564-ae4d-4874-a3e0-25f0f83b4c80.png)

dup中如果不想给初值,可以直接写 ?

###### 赋值

```
g_arydw12 dup(0)


;给数组元素赋值
lea bx , g_ary;获取 g_ary 的偏移地址,即第一个元素的地址
movword ptr ,10h ; word是数组元素类型 2*2 是 元素下标 * 元素大小
                               ; 计算元素偏移值 , 10H 是要赋值的值
                                 
```

###### 取值

```
g_arydw12 dup(0)


;取出数组指定元素的值
lea bx , g_ary;获取 g_ary 的偏移地址,即第一个元素的地址
movax,word ptr ; word是数组元素类型 2 * 2 是 元素下标 * 元素大小
                              ; 计算元素偏移值
```

##### 属性

masm提供了很多伪指令,可以获取变量的大小和地址,称之为变量的属性。

!(./notesimg/1652368887037-76659772-c81d-4aeb-ae1a-fcf270ca4c1c.png)

```
;这里是数据
data segment    ;变量不允许重名
   g_arydw12 dup(0)
   ; 如果 g_arydw888h,12 dup(0)那么属性就是以g_ary dw 888h来算
   g_bt   db11h

data ends

;这里是代码
codesegment   ;段开始

START:;标号
    assume ds:data   ;指定data段 作为 ds 段
    mov ax,data      ;先把data的偏移值给ax
    mov ds,ax      ;ax再把data的偏移值给ds
                     ;不直接给是因为没有立即数到段寄存器

    mov ax, set    g_bt   ;把变量g_bt的段基址给 ax
    mov ax, size   g_bt   ;将变量g_bt的大小给 ax
    mov ax, type   g_bt   ;将变量g_bt的元素类型大小给 ax
    mov ax, length g_bt   ;将变量g_bt的元素个数给 ax

    mov ax, size   g_ary;将变量g_ary的大小给 ax
    mov ax, type   g_ary;将变量g_ary的元素类型大小给 ax
    mov ax, length g_ary;将变量g_ary的元素个数给 ax

   ;给数组元素赋值
   lea bx , g_ary;获取 g_ary 的偏移地址,即第一个元素的地址
   movword ptr ,10h ; word是数组元素类型 2*2 是 元素下标 * 元素大小
                                    ; 计算元素偏移值 , 10H 是要赋值的值

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652369885872-46c6ebf0-1cc9-466d-93f5-6f38b06aec0e.png)

##### 堆栈

stack关键字让程序在被加载的时候指定ss、bp和sp 。

使用数组为栈设置大小

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名
   g_arydw12 dup(0)
   g_bt   db11h


data ends

;这里是代码
codesegment   ;段开始

START:;标号
    assume ds:data   ;指定data段 作为 ds 段
    mov ax,data      ;先把data的偏移值给ax
    mov ds,ax      ;ax再把data的偏移值给ds
                     ;不直接给是因为没有立即数到段寄存器

    mov ax, seg    g_bt   ;把变量g_bt的段基址给 ax
    mov ax, size   g_bt   ;将变量g_bt的大小给 ax
    mov ax, type   g_bt   ;将变量g_bt的元素类型大小给 ax
    mov ax, length g_bt   ;将变量g_bt的元素个数给 ax

    mov ax, size   g_ary;将变量g_ary的大小给 ax
    mov ax, type   g_ary;将变量g_ary的元素类型大小给 ax
    mov ax, length g_ary;将变量g_ary的元素个数给 ax

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652370902699-ea856664-2493-4834-9a02-f94488ed1d91.png)

#### 6. 调用dos功能号

##### 功能号

1.dos系统提供的功能(API),通过21号中断来调用

2.每个功能都有一个编号,通过AH指定功能号

3.每个功能的参数查看手册

[📎指令字典2005II.zip](https://www.yuque.com/attachments/yuque/0/2022/zip/27242010/1652372237477-6a2b0f69-f677-4fb8-a552-e004e86d0550.zip)

!(./notesimg/1652370995818-5c45ddc5-b8bf-47be-b25d-18dd667f9eb9.png)!(./notesimg/1652372330779-31765e29-ca6a-4f48-9b63-44503e70d0c8.png)!(https://cdn.nlark.com/yuque/0/2022/png/27242010/1652372371971-36f4e817-cfc6-4ee6-8d8c-529f9191daa3.png)

用的最多的是21号 中断

###### 程序结束

!(./notesimg/1652372998411-35191e1e-1eed-4d1a-bc0d-83e862b2fbe6.png)

使用方法:   将功能编号 给 al在 调用 int21

```
;mov ah,4CH    ;程序结束
    ;mov al,00   ;返回值 类似于 return 0

    mov ax,4C00H; 上面2条指令合成一条
    int 21h   ;使用21号中断
```

!(./notesimg/1652373138724-487b97ed-b835-4b43-bf98-698fdfcbde79.png)

6输出一个字符串

DS:减一串地址

显示字符串

60

$'结束字符串

!(./notesimg/1652373222195-67248dbf-ac07-40d3-8fec-30a496fcee23.png)

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名
    g_sz db "hello world",0dh,0ah,"$"   ;用于输出的字符串 0dh \r0ah \n
data ends

;这里是代码
codesegment   ;段开始

START:;标号
    assume ds:data   ;指定data段 作为 ds 段
    mov ax,data      ;先把data的偏移值给ax
    mov ds,ax      ;ax再把data的偏移值给ds
                     ;不直接给是因为没有立即数到段寄存器
   
   lea dx,g_sz         ;获取字符串 g_sz 的首地址
   ;mov dx, offsetg_sz;获取字符串 g_sz 的段偏移值,即首地址跟上面效果一样

   mov ah,9   ; 将功能编号给ah
   int 21H   ;调用21号中断

    ;mov ah,4CH    ;程序结束
    ;mov al,00   ;返回值 类似于 return 0

    mov ax,4C00H; 上面2条指令合成一条
    int 21h   ;使用21号中断

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652373990507-253d8b1d-9c00-49fb-8002-a8617f2091db.png)

###### 输入字符串

!(./notesimg/1652374043418-521ec12a-7ce0-4a5e-9c4d-1ca4b4b8d1a6.png)

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名
g_sz db 32 dup(0)   ;用于输入的字符串
data ends

;这里是代码
codesegment   ;段开始

START:;标号
    assume ds:data   ;指定data段 作为 ds 段
    mov ax,data      ;先把data的偏移值给ax
    mov ds,ax      ;ax再把data的偏移值给ds
                     ;不直接给是因为没有立即数到段寄存器
   
   lea dx,g_sz         ;获取字符串 g_sz 的首地址
   ;mov dx, offsetg_sz;获取字符串 g_sz 的段偏移值,即首地址跟上面效果一样

   mov byte ptr ,sizeg_sz;把字符串大小给byte ptr
   mov dx,bx                     ;把地址给 dx

   mov ah,0aH   ; 将功能编号给ah
   int 21H   ;调用21号中断

    ;mov ah,4CH    ;程序结束
    ;mov al,00   ;返回值 类似于 return 0

    mov ax,4C00H; 上面2条指令合成一条
    int 21h   ;使用21号中断

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652375176444-2a87cd1f-4665-4116-802f-b94f404bdd6e.png)

##### 中断

1.中断是由cpu提供的流程跳转指令,类似函数调用

2.在00:00位置存储着一个双字数组,大小为256,称作中断向量表

3.数组元素为逻辑地址**段基址****:****段偏移**

4.int n的意思是从第n个元素获取地址,然后跳转执行

!(./notesimg/1652375384172-318cdc6c-fc3d-47f9-80db-a20899c75c34.png)

!(./notesimg/1652375391781-6431e2a4-36f3-4fa4-a674-8ec247b866b4.png)

!(./notesimg/1652375640936-6dad8a4c-75b5-43f3-ba4a-7c6079e72049.png)

#### 总结

每一个文件 以 **end** 作结尾,每个文件至少有一个段,程序的入口点用标号 ,标号名放在end后面.多个文件只能有一个标号放在end后面,一个段里面可以定义变量,可以写代码,但是一般我们会把,代码,数据,栈分开,放在不同段里面

#### 示例:

!(./notesimg/1652356083047-422ae80a-94e5-463a-af93-4b6653b21cd6.png)

```
codesegment   ;段开始

START:;标号
movax,ax
movax,ax
movax,ax

code ends      ;段结束

endSTART;代表从标号START 的第一行代码作为运行起点
```

!(./notesimg/1652357063190-e6c14824-5a96-4776-9e9d-52016c80f418.png) 注意文件要放在挂在的文件中

###### 编译

!(./notesimg/1652357256557-dfbc6b0c-8407-40b7-ad57-6d5dfa67fbaa.png)

!(./notesimg/1652357337742-af35d59a-8a37-4eb2-9f28-cd205e51eaa8.png)

!(./notesimg/1652357374753-74572548-877e-42aa-8ddd-f01416885fa5.png)

!(./notesimg/1652357470425-7329e56a-e0b6-4a5f-8f15-6d6a69ab20be.png)

###### 链接

!(./notesimg/1652357595611-7d42726e-3403-4949-9d8f-e6464d2842d2.png)

!(./notesimg/1652357633091-b01efac3-59e8-4bbb-8dc4-310b5ec705f0.png)

###### 调试

!(./notesimg/1652357900919-274df98c-fe80-4c51-b3eb-a948befbb552.png)

###### 编译链接脚本

```
● 编译+调试 脚本
ml/c %1.asm
link %1.obj
debug %1.exe

文件后缀要改成 .bat ,而且要跟文件同目录
```

!(./notesimg/1652358807786-936a689f-8137-4c98-ad49-8ccc9ffbfdde.png)

!(./notesimg/1652358920287-1395e154-8426-4be7-955a-6bc47821286c.png)

### 串操作

• 串传送MOVS(move string)

• 串存储STOS(store string)

• 串读取LODS(load string)

• 串比较CMPS

• 串扫描SCAS(scan string)

串传送MOVS(move string)

```
把字节或字操作数从主存的源地址传送至目的地址
```


(1)MOVSB

```
作用:字节 串传送:ES:←DS: ( SI←SI±1,DI←DI±1 )
```


```
从 DS: 取一个字节 存到ES:
```


(2)MOVSW

```
作用:字串传送:ES:←DS:(SI←SI±2,DI←DI±2)
```


```
从 DS: 取一个字 存到ES:
```


(3)MOVSD

```
作用:双字串 传送:ES:←DS:(SI←SI±4,DI←DI±4)
```


```
从 DS: 取一个双字 存到ES:
```


举例:

```
memcpy
```


```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器
                  
   lea si,g_sz       ;获取字符串 g_sz 的首地址
   lea di,g_sz1      ;获取字符串 g_sz1 的首地址

   mov ax,ds      ;因为是从 ES:←DS:,所以把 es设成ds
   mov es,ax
   movsb            ;从DS:拷贝一个字节数据到ES:
   movsb            ;从DS:拷贝一个字节数据到ES:
   movsb            ;从DS:拷贝一个字节数据到ES:
   movsb
   movsb
   movsb

   ;mov ah,4CH    ;程序结束
   ;mov al,00   ;返回值 类似于 return 0

   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652377487344-747947d3-cf97-45ea-9abf-a1563fd3f0e7.png)

串存储STOS(store string)

把AL或AX数据传送至目的地址

(1)STOSB

```
作用:字节串存储 ES:←AL    DI←DI±1
```


```
把Al中的值给   ES:
```


(2)STOSW

作用:字串存储:ES:←AX    DI←DI±2

```
把AX 中的值给   ES:
```


(3) STOSD

```
作用:双字串存储:ES:←EAX   DI←DI±4
```


```
把 EAX   中的值给ES:
```


举例:

```
memset
```


```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器

   mov ax,ds      
   mov es,ax

   mov al,66h
   lea di,g_sz1      ;获取字符串 g_sz 的首地址
   stosb             ;将 al (66H),依次赋值给 g_sz1 开始的 各个字节
   stosb
   stosb
   stosb

   ;mov ah,4CH    ;程序结束
   ;mov al,00   ;返回值 类似于 return 0

   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

#### !(./notesimg/1652380395149-4797d9e8-5741-4d3f-96ea-5ff9976a8b58.png)

####

#### 串读取LODS(load string)

```
把指定主存单元的数据传送给AL或AX
```


##### (1)LODSB

```
作用:字节串读取 AL←DS:(SI←SI±1)
```


```
从DS:读取一个字节 给 al
```


##### (2)LODSW

```
作用:字串读取 AX←DS: (SI←SI±2)
```


##### (3)LODSD

```
作用:双字串读取 EAX← DS: (SI←SI±4)
```


```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
g_sz2 db "hi$"
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器

   mov ax,ds      
   mov es,ax

   mov al,0
   lea si,g_sz      ;获取字符串 g_sz 的首地址
   lodsb
   lodsb
   lodsb

   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束
endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652382371120-9e4dc0d7-cae8-4ca1-835e-4408a307692f.png)

#### 串比较CMPS

将主存中的源操作数减去至目的操作数,以便设置标志,进而比较两操作数之间的关系

##### (1)CMPSB

作用:字节串比较:DS:-ES:( SI←SI±1,DI←DI±1 )

##### (2)CMPSW

作用:字串比较:DS:-ES:(SI←SI±2,DI←DI±2)

##### (3)CMPSD

作用:双字串比较:DS:-ES: ( SI←SI±4,DI←DI±4 )

举例:

strstr

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
g_sz2 db "hi$"
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器

   mov ax,ds      
   mov es,ax

   lea si,g_sz      ;获取字符串 g_sz 的首地址
   lea di,g_sz2      ;获取字符串 g_sz2 的首地址
   cmpsb
   cmpsb


   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束

endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652381188983-a3ab60f4-981b-4cd2-83be-56056ee6821e.png)

#### 串扫描SCAS(scan string)

•作用:将**AL/AX**减去至目的操作数,以便设置标志,进而比较AL/AX与操作数之间的关系

```
一般用来找某个字符,确定字符串长度 ,例如 16asm 字符串以 "
```

$" 结束 , 那我们从字符串开始,到找到 '$'

Di 的 值,就是字符串长度, 或者判断字符串中是否有某个字符,或者字符串中某个字符的下标

##### (1)SCASB

作用:字节串扫描:AL-ES:(DI←DI±1)

##### (2)SCASW

作用:字串扫描:AX-ES:(DI←DI±2)

##### (3) SCASD

作用:字串扫描:EAX-ES:(DI←DI±4)

举例:strlen

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
g_sz2 db "hi$"
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器

   mov ax,ds      
   mov es,ax

   mov al,'l'
   lea di,g_sz      ;获取字符串 g_sz 的首地址

   scasb
   scasb
   scasb
   scasb


   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束
endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652381934325-e0c972c0-e6cb-40fa-9aa7-624df8c4de85.png)

#### 重复前缀指令

只能用于串操作指令

串操作指令执行一次,仅对数据串中的一个字节或字进行操作。

串操作指令前,都可以加一个重复前缀,实现串操作的重复执行。重复次数隐含在**CX**寄存器中。

重复前缀分2类,3条指令:

配合不影响标志的 MOVS、STOS(和LODS)指令的 REP 前缀

配合影响标志的 CMPS 和 SCAS 指令的 REPZ 和 REPNZ 前缀

##### 无条件重复前缀指令REP

每执行一次串指令,CX减1,直到CX=0,重复执行结束。

理解为:当数据串没有结束(CX≠0),则继续传送。

举例:

REPLODS/LODSB/LODSW/LODSD

REPSTOS/STOSB/STOSW/STOSD

REPMOVS/MOVSB/MOVSW/MOVSD

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
g_sz2 db "hi$"
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器

   mov ax,ds      
   mov es,ax


   lea si,g_sz      ;获取字符串 g_sz 的首地址
   lea di,g_sz1   ;获取字符串 g_sz1 的首地址
   mov cx, offset g_sz1 - offset g_sz;计算字符串 g_sz 长度
   
   rep movsb    ;将 字符串g_sz的内容 全部拷贝到g_sz1 , cx等于 g_sz 长度



   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束
endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652383905221-92da013a-c2ae-4f42-812d-f2915bc26dd5.png)

##### 条件重复前缀指令REPZ

```
每执行一次串指令,CX减1。
```


```
```

**并判断ZF是否为0。**

```
只要CX=0或ZF=0,重复执行结束。
```


•理解:当数据串没有结束(CX≠0),并且串相等(ZF=1),则继续比较。

•举例:

```
REPE/REPZ      SCAS/SCASB/SCASW/SCASD
```


```
REPE/REPZ      CMPS/CMPSB/CMPSW/CMPSD
```


作用: 比较2个字符串是否一样 ,如果CF 等于0 就代表一样

```
;栈段
stack segmentstack   ; segment 后面跟关键字stack ,说明这是一个栈段
   db256 dup(1)
stack ends

;这里是数据
data segment    ;变量不允许重名

g_sz db "hello world",0dh,0ah,"$"   
g_sz1 db 32 dup(0)   ;用于输入的字符串
g_sz2 db "hello$"
g_sz3 db "hello world",0dh,0ah,"$"
data ends

;这里是代码
codesegment   ;段开始

START:;标号
   assume ds:data   ;指定data段 作为 ds 段
   mov ax,data      ;先把data的偏移值给ax
   mov ds,ax      ;ax再把data的偏移值给ds
                  ;不直接给是因为没有立即数到段寄存器

   mov ax,ds      
   mov es,ax


   lea si,g_sz      ;获取字符串 g_sz 的首地址
   lea di,g_sz2   ;获取字符串 g_sz2 的首地址
   mov cx, offset g_sz1 - offset g_sz;计算字符串 g_sz 长度
   
   repz cmpsb    ;ZF位是0 或者 CX = 0 结束

   lea si,g_sz      ;获取字符串 g_sz 的首地址
   lea di,g_sz3   ;获取字符串 g_sz2 的首地址
   mov cx, offset g_sz1 - offset g_sz;计算字符串 g_sz 长度
   repz cmpsb    ;ZF位是0 或者 CX = 0 结束



   mov ax,4C00H; 上面2条指令合成一条
   int 21h   ;使用21号中断

code ends      ;段结束
endSTART;代表从该标号第一行代码作为运行起点
```

!(./notesimg/1652384605511-d04c3ea0-3cc3-466c-95d3-fdb0696bdd84.png)

!(./notesimg/1652384679206-d2345593-5bd7-4166-8415-5b047306cba4.png)

##### 条件重复前缀指令REPNZ

每执行一次串指令,CX减1。

```
```

**并判断ZF是否为1**。

```
只要CX=0 或ZF=1,重复执行结束。
```


•理解:当数据串没有结束(CX≠0),并且串不相等(ZF=0),则继续比较。

•举例:

```
REPNE/REPNZSCAS/SCASB/SCASW/SCASD
```


```
REPNE/REPNZ   CMPS/CMPSB/CMPSW/CMPSD
```


### 作业

#### 1、SCASB指令的作用是什么?叙述指令REPESCASB指令所完成的功能。

```

```

**AL/AX**减去至目的操作数,以便设置标志,进而比较AL/AX与操作数之间的关系

```
REPESCASB : 可以用来找某个字符,确定字符串长度 ,例如 16asm 字符串以 "
```

$" 结束 , 那我们从字符串开始,到找到 '$'Di 的 值,就是字符串长度, 或者判断字符串中是否有某个字符,或者字符串中某个字符的下标

#### 2、指令REPNESCASB结束执行的条件是什么?

```
数据传中的字符 和 al 中的值相等 或者 cx 值为0
```


#### 3、REP前缀的作用是什么?能用指令REP LODSB读取DS:SI所指内存中的每个字符来进行处理吗?若不能,请说明原因。

```
重复执行串操作      不行,al中的值会被后面的覆盖
```


#### 4、 从键盘读入任意字符串(不超过255),并输出字符串长度和字符串内容

```
;从键盘读入任意字符串(不超过255),并输出字符串长度和字符串内容
;这里是数据
data segment    ;变量不允许重名
g_w dw   0   ;记录循环次数
g_sz1 db 6 dup('$')   ;用于输出长度的字符串
g_sz db 255 dup('$')   ;用于输入的字符串
g_Number db "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F" ; 数字数组
data ends

;这里是代码
codesegment   ;段开始

START:;标号
    assume ds:data   ;指定data段 作为 ds 段
    mov ax,data      ;先把data的偏移值给ax
    mov ds,ax      ;ax再把data的偏移值给ds
    mov es,ax      ;不直接给是因为没有立即数到段寄存器
                        

   lea bx,g_sz         ;获取字符串 g_sz 的首地址
   mov word ptr ,sizeg_sz;把字符串大小给byte ptr
   mov dx,bx                     ;把地址给 dx
;输入字符串
   mov ah,0aH   ; 将功能编号给ah
   int 21H   ;调用21号中断

   mov ax,0
   mov al,byte ptr
   mov si,ax
   mov word ptr ,0d0aH   ;添加字符串后加'\r\n'

   lea dx,g_sz         ;获取字符串 g_sz 的首地址
   add dx,2
   ;输出字符传串
   mov ah,9   ; 将功能编号给ah
   int 21H   ;调用21号中断

    ;将长度给g_sz1
    ;把DX清0
    mov dx,0
    mov dl,byte ptr
    ;然后取出低四位去数组里找到对应的存储
    mov cl,4
    shr dx,cl;右移四位得到低四位

    mov si,dx
    mov cl,g_Number ;把数组第bx个元素给cx寄存器
    ;然后把cl里的值给输出数字的字符串
    mov byte ptr ,cl

    ;开始取第二位
    mov dx,0;
    mov dl,byte ptr
    mov cl,12
    shl dx,cl
    shr dx,cl
    mov si,dx
    mov cl,g_Number ;把数组第bx个元素给cx寄存器

    ;然后把cl里的值给输出数字的字符串
    mov byte ptr ,cl
    mov word ptr ,0d0aH   ;添加字符串后加'\r\n'

    lea dx,g_sz1         ;获取字符串 g_sz 的首地址
    ;输出字符传串
    mov ah,9   ; 将功能编号给ah
    int 21H   ;调用21号中断


EXIT:
    mov ax,4C00H; 上面2条指令合成一条
    int 21h   ;使用21号中断

code ends      ;段结束
endSTART;代表从该标号第一行代码作为运行起点
```

#### 5、 从键盘读入任意字符串(不超过255),以第一个字母为文件名,将字符串存入文件。

```
data segment

g_sz db 255 dup("$")
g_szfileNamedb 'a.txt', 0

data ends

;这里是代码
CODE segment

START:
    assume ds:data
    mov ax, data
    mov ds, ax

    ;输入字符串
    lea bx, g_sz   ;获取字符串首地址
        mov byte ptr , size g_sz   ;存储字符串大小
        mov dx, bx
        mov ah, 0ah       ;调用输入文件功能
    int 21h

    ;创建文件
        lea bx, g_sz      ;获取字符串首地址
        mov cl,       ;获取字符串的第一个字符
        mov g_szfileName, cl      ;将字符串的第一个字符给文件名
        mov dx, offset g_szfileName ;获取文件名首地址
        mov cx, 0         ;文件属性 0 即可
        mov ah, 3ch         ;调用创建文件功能
        int 21h                     

    ;写文件    DS:DX=数据缓冲区地址   BX=文件代号(创建文件后ax的值)    CX=写入的字节数

        lea dx,         ;获取字符串开始地址 前面2字节是 总大小 和 实际大小
        mov bx, ax   
        lea si, g_sz
        mov cl,       ;获取字符串实际大小 首地址 +1

        mov ah, 40h         ;调用写文件功能
        int 21h                     

        mov ah, 3eh
        int 21h                ;关闭文件


        mov ax, 4c00h
        int 21h
      
CODE ends
end START
```

#### 6、 从键盘读入文件名,打开文件(不超过255),显示文件内容

```
data segment
g_sz db 255 dup("$")
g_szFileName db 255 dup("0")
data ends

;这里是代码
CODE segment

START:
    assume ds:data
    mov ax, data
    mov ds, ax

    ;输入字符串
    lea bx, g_szFileName   ;获取字符串首地址
        mov byte ptr , size g_szFileName   ;存储字符串大小
        mov dx, bx
        mov ah, 0ah            ;调用输入文件功能
    int 21h

    ;清除输入的回车
    mov ax,0
    mov al,byte ptr
    mov si,ax
    mov byte ptr ,0   ;添加字符串后加0


    ;打开文件 DS:DX=文件名地址AL=0 读=1 写=3 读/写
        lea dx, g_szfileName      ;获取字符串首地址
    add dx,2
        mov al, 0h         ;文件属性 0 读1 写3 读/写
        mov ah, 3dh         ;调用创建文件功能
        int 21h


    ;读文件    DS:DX=数据缓冲区地址   BX=文件代号(创建文件后ax的值)    CX=读取的字节数
    lea dx, g_sz;字符串缓冲区
    mov bx, ax
    mov cl, 255   ; 读取字符的长度
    mov ah, 3fh
    int 21h    ;读入字符串内容

        mov ah, 3eh
        int 21h                        ;关闭文件

    lea dx,g_sz         ;获取字符串 g_sz 的首地址
    ;输出字符传串
    mov ah,09H   ; 将功能编号给ah
    int 21H   ;调用21号中断


        mov ax, 4c00h
        int 21h
      
CODE ends
end START
```

#### 7、从键盘输入文件名,查找指定文件(不超过255)中是否含有字母B。

!(./notesimg/1652389980805-a4007e04-dd21-4549-b1b9-bea2e0ec7064.png)

```
data segment
g_IsHave db "Is Have",0dh,0ah,"$" ;存在B
g_IsNotHave db "No Have",0dh,0ah,"$";不存在B
g_sz db 255 dup("$")
g_szFileName db 255 dup("0")
data ends

;这里是代码
CODE segment
START:
    assume ds:data
    mov ax, data
    mov ds, ax

    ;输入字符串
    lea bx, g_szFileName   ;获取字符串首地址
        mov byte ptr , size g_szFileName   ;存储字符串大小
        mov dx, bx
        mov ah, 0ah            ;调用输入文件功能
    int 21h

    ;清除输入的回车
    mov ax,0
    mov al,byte ptr
    mov si,ax
    mov byte ptr ,0   ;添加字符串后加0


    ;打开文件 DS:DX=文件名地址AL=0 读=1 写=3 读/写
        lea dx, g_szfileName      ;获取字符串首地址
    add dx,2
        mov al, 0h         ;文件属性 0 读1 写3 读/写
        mov ah, 3dh         ;调用创建文件功能
        int 21h


    ;读文件    DS:DX=数据缓冲区地址   BX=文件代号(创建文件后ax的值)    CX=读取的字节数
    lea dx, g_sz;字符串缓冲区
    mov bx, ax
    mov cl, 255   ; 读取字符的长度
    mov ah, 3fh
    int 21h    ;读入字符串内容


        mov ah, 3eh
        int 21h                        ;关闭文件

    mov al,'b'
    lea di,g_sz      ;获取字符串 g_sz 的首地址


    REPNZ scasb    ;串扫描,cx=0 或则 zf = 1 停止

    ; zf =1 时跳转
    jzLEABEL2    ;扫描过后判断zf状态是否为1 ,不判断cf是防止最后一个是b

    ;输出字符串g_szNEqu
    lea dx,g_IsNotHave
    mov ah,09H
    int 21H

EXIT:
    mov ax,4C00H; 上面2条指令合成一条
    int 21h   ;使用21号中断

LEABEL2:
    ;输出字符串g_szEqu
    lea dx,g_IsHave
    mov ah,09H
    int 21H
    jmp EXIT
          
CODE ends
end START
```

#### 8、从键盘读入文件名,打开文件(不超过255),输出文件中字符的个数。

!(./notesimg/1652389980805-a4007e04-dd21-4549-b1b9-bea2e0ec7064-170079276346856.png)

```
data segment
g_dwLengthdw ?   ;字符串长度
g_szLength db 8 dup('$')   ;用于输出长度的字符串
g_szEnter db 0dh,0aH,'$'    ;回车换行
g_Number db "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F" ; 数字数组
g_sz db 255 dup("$")
g_szFileName db 255 dup("0")
data ends

;这里是代码
CODE segment
START:
    assume ds:data
    mov ax, data
    mov ds, ax

    ;输入字符串
    lea bx, g_szFileName   ;获取字符串首地址
        mov byte ptr , size g_szFileName   ;存储字符串大小
        mov dx, bx
        mov ah, 0ah            ;调用输入文件功能
    int 21h

    ;清除输入的回车
    mov ax,0
    mov al,byte ptr
    mov si,ax
    mov byte ptr ,0   ;添加字符串后加0


    ;打开文件 DS:DX=文件名地址AL=0 读=1 写=3 读/写
        lea dx, g_szfileName      ;获取字符串首地址
    add dx,2
        mov al, 0h         ;文件属性 0 读1 写3 读/写
        mov ah, 3dh         ;调用创建文件功能
        int 21h


    ;读文件    DS:DX=数据缓冲区地址   BX=文件代号(创建文件后ax的值)    CX=读取的字节数
    lea dx, g_sz;字符串缓冲区
    mov bx, ax
    mov cl, 255   ; 读取字符的长度
    mov ah, 3fh
    int 21h       ;读入字符串内容
   
    mov g_dwLength,ax;保存读取文件长度

        mov ah, 3eh
        int 21h                        ;关闭文件

    ;调一下回车
    lea dx,g_szEnter         ;获取字符串 g_szEnter 的首地址
    ;输出字符传串
    mov ah,9   ; 将功能编号给ah
    int 21H   ;调用21号中断


    ;将长度给g_szLength
    ;把DX清0
    mov dx,g_dwLength
    ;然后取出低四位去数组里找到对应的存储
    mov cl,4
    shr dx,cl;右移四位得到低四位

    mov si,dx
    mov cl,g_Number ;把数组第bx个元素给cx寄存器
    ;然后把cl里的值给输出数字的字符串
    mov byte ptr ,cl

    ;开始取第二位
    mov dx,g_dwLength;
    mov cl,12
    shl dx,cl
    shr dx,cl
    mov si,dx
    mov cl,g_Number ;把数组第bx个元素给cx寄存器

    ;然后把cl里的值给输出数字的字符串
    mov byte ptr ,cl
    mov word ptr ,0d0aH   ;添加字符串后加'\r\n'

    lea dx,g_szLength         ;获取字符串 g_sz 的首地址
    ;输出字符传串
    mov ah,9   ; 将功能编号给ah
    int 21H   ;调用21号中断

EXIT:
    mov ax,4C00H; 上面2条指令合成一条
    int 21h   ;使用21号中断
   
CODE ends
end START
```

#### 9、 程序中定义一个数组(不超过255),从键盘输入一个字符和长度,将数组中输入长度的部分设置成输入的字符。

```
;程序中定义一个数组(不超过255),从键盘输入一个字符和长度,将数组中输入
;长度的部分设置成输入的字符。



data segment
g_sz_input db 4 dup(0),0ah,0dh,'$'
g_length db 4 dup(0),0ah,0dh,'$'
g_sz db 255 dup(0)

g_szInputLength db 'please input length',0ah,0dh,'$'
g_szInputChar db 'please input char',0ah,0dh,'$'
g_szEnther db0ah,0dh,'$'
data ends

;这里是代码
CODE segment

START:
    assume ds:data
    mov ax, data
    mov ds, ax

    lea bx, g_szInputChar
        mov dx, bx
        mov ah, 09h
    int 21h

    lea bx, g_sz_input
        mov byte ptr , size g_sz_input
        mov dx, bx
        mov ah, 0ah
    int 21h                                                ;输入字符

    lea bx, g_szInputLength
        mov dx, bx
        mov ah, 09h
    int 21h

        lea bx, g_length
        mov byte ptr , size g_length
        mov dx, bx
        mov ah, 0ah
    int 21h                                                ;输入长度


    ;需要把对应字符串转为字符,尚未实现
    ;思路根据长度取出字符串,根据字符在数组中的值在字符数组偏移值求索引(即在数组中的下班)



        mov ax, 0
        mov bx, 0
        mov si, 0
        mov al,
        mov bl,
        mov si, ax
        mov , bl                        ;将字符赋值到相应位置


        mov ax, 4c00h
        int 21h
   
CODE ends

end START
```
页: [1]
查看完整版本: 8086汇编(16位汇编)学习笔记05.asm基础语法和串操作