8086AssemblyNote

8086汇编复习手册

声明 : 本文全部内容的背景均为80x86平台;文中标点中英混用,懒得改;文中代码基本都经实际验证过。文章内容均个人(PGZXB pgzxb@qq.com)整理,未经校正,欢迎勘误!

8086平台基础

大小端

  • 8086平台为小端机, 即低字节存放在低地址, 高字节存放在高地址

    寄存器

  1. 通用寄存器

    (1) 数据寄存器

      AX: 累加器, 所有的I/O指令都使用累加器与外设接口传送信息;

      BX: 基址寄存器, 可用来存放偏移地址;

      CX: 计数寄存器, LOOP指令用作计数器;

      DX: 数据寄存器;

      注: DX和AX常用来作为整体存放32位数, 高位存在DX, 低位存在AX (MUL指令、CWD指令)。

    (2) 地址指针寄存器

      SP: 堆栈指针寄存器,指向堆栈的栈顶;

      BP: 基址指针寄存器,通常是与SS寄存器配对使用。

    (3)变址寄存器

      SI: 源变址寄存器。

      DI: 目的变址寄存器。

      它们常常在处理数组(或串)中作为索引(Index)。

    注意: 通用寄存器是通用的,都可用来存放普通数据(地址寄存器存个数字也是可以的),但以上三类每一类寄存器都专有用途。

  2. 段寄存器

  CS: 代码段寄存器, 指令存放在代码段内

  SS: 堆栈段寄存器, PUSH, POP

  DS: 数据段寄存器, 变量(常量)一般都定义于数据段

  ES: 附加段寄存器, 不常用,串指令用到过

  存放段基地址, 即段起始地址的高16位。

  1. 注意事项
    • CS不能被指令修改
    • 只有BX, BP, SI, DI能当作地址寄存器被使用, SP能作地址寄存器但是不能被使用(修改)
    • 不是地址寄存器的寄存器也可以用来存地址(所谓地址只是16位无符号数而已), 只不过不能用来寄存器寻址、寄存器相对寻址、基址变址寻址、相对基址变址寻址(总之就是用寄存器存地址的寻址方式都不能*
    • BP默认段寄存器是SS, 其他默认均为DS

标志位

  • 注意: 标志位设置和指令有关, 当指令要求与一般规律冲突时, 以指令要求为准.
标志名 英文简称 标志表示 1/0 标志位设置的一般规律
溢出 OF OV/NV 正 + 正 得 负 时为1
负 + 负 得 正 时为1
正 - 负 得 负 时为1
负 - 正 得 正 时为1
方向 DF DN/UP
中断 IF EI/DI
符号 SF NG/PL 符号位为1时为1
符号位为0时为0
ZF ZR/NZ 运算结果为0时为1
辅助进位 AF AC/NA
奇偶 PF PE/PO 结果最后一个字节
二进制形式1的个数
为偶数时为1, 反之为0
进位 CF CY/NC 最高位有进位(借位)时为1, 反之为0
  • 标志位的设置总结(每条指令的标志位的设置查看指令速查表):

    • 数据传送类指令除了标志寄存器传送指令不影响标志位

    • INC, DEC不影响CF, 其他标志位的设置按一般规律

    • 加减法指令除了INC, DEC设置标志位均按一般规律

    • 乘法指令如果乘积结果的高一半为0则设置CF=OF=0, 否则设置CF=OF=1. 乘法指令对于CF和OF以外的标志位设置无定义(无定义不意味不影响)

    • 除法指令对所有标志位的设置均无定义

    • NOT指令不影响标志位

    • 除NOT以外的逻辑指令(& | ^ TEST)设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律

    • 移位指令对标志位的设置比较复杂

      • 移位指令都会影响CF, 影响方式如下图

        image-20210626125041018

      • 移位指令只有当CNT为1时才会设置OF, 高位变换OF=1, 否则OF=0

      • 循环移位指令不影响除CF,OF以外的标志位

      • 循环移位以外的移位指令对AF无定义, 其他标志位的设置按一般规律

    • 串传送 MOVSB / MOVSW不影响条件码。

    • 存串 STOSB / STOSW 不影响条件码。

    • 取串LODSB / LODSW不影响条件码。

    • 串比较 CMPSB / CMPSW 对条件码的影响 : 看书

    • 串扫描 SCASB / SCASW对条件码的影响 : 看书

寻址方式及其使用和注意事项

  • 寻址方式一共七种
    • 立即寻址方式
    • 寄存器寻址方式
    • 直接寻址方式
    • 寄存器间接寻址方式
    • 寄存器相对寻址方式(常用于处理数组)
    • 基址变址寻址方式
    • 相对基址变址寻址方式
寻址方式 示例 注意事项
立即寻址方式 MOV AX, 10H
寄存器寻址方式 MOV AX, BX
直接寻址方式 MOV AX, [2000H]
寄存器间接寻址方式 MOV AX, DS:[BX] only BX BP SI DI
寄存器相对寻址方式 MOV AX, ARR[SI]
MOV AX, [ARR + SI]
only BX BP SI DI
基址变址寻址方式 MOV AX, [BX][DI] 基址寄存器 : BX BP
变址寄存器 : SI DI
相对基址变址寻址方式 MOV AX, ARR[BX][DI]
MOV AX, ARR[BX + DI]
MOV AX, [ARR + BX + DI]
基址寄存器 : BX BP
变址寄存器 : SI DI

8086对段寄存器使用的约定

序号 内存访问类型 默认段寄存器 可重设的段寄存器 段内偏移地址来源
1 取指令 CS IP
2 堆栈操作 SS SP
3 串操作之源串 DS ES、SS SI
4 串操作之目标串 ES DI
5 BP用作基址寻址 SS ES、DS 按寻址方式计算得有效地址
6 一般数据存取 ES ES、SS 按寻址方式计算得有效地址

注: 除BP外其他可用来当地址寄存器的默认段均为DS

8086汇编模板(熟练记忆, 考试要写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此
DATA ENDS ; ENDS : END of SEGMENT

CODE SEGMENT ; 代码段

ASSUME DS: DATA, CS: CODE ; 声明DATA段是DS段, CODE段是CS段

;; 一般在这里定义函数
;; 示例:
;;;; FUNC PROC ; FUNC是函数名
;;;; ;; 函数代码
;;;; RET ; return
;;;; FUNC ENDP

START: ; 程序从这里开始执行

; 手动设置DS的值
MOV AX, DATA
MOV DS, AX

; 写自己的代码
;

; 调用DOS中断返回DOS
MOV AH, 4CH
INT 21H
CODE ENDS
END START

8086汇编指令速查表

指令 作用 对标志位的影响 备注
MOV MOV 1, 2
将2的内容移到1
不影响 不能直接MOV立即数到段寄存器
不能修改CS寄存器
两个操作数不能都是内存(CMP, XCHG也是)
PUSH 入栈 不影响 只能操作字; 不能搞立即数
POP 出栈 不影响 只能操作字; 不能搞立即数
XCHG 交换两个的值 不影响 两个操作数不能都是内存
两个操作数必须有一个在寄存器中
操作数不能使用段寄存器
两个操作数类型要匹配
IN 输入 不影响 仅限于AX,AL传送信息
用法:
IN AL, PORT(字节)
IN AX, PORT(字节)
IN AL, DX(字节)
IN AX, DX(字)
OUT 输出 不影响 仅限于AX,AL传送信息
用法:
OUT PORT, AL(字节)
OUT PORT, AX(字节)
OUT DX, A(字节)L
OUT DX, AX(字)
XLAT 换码指令 不影响
LEA 有效地址送寄存器 不影响 目的操作数可使用16/32位寄存器, 不能使用段寄存器

操作数长度, 地址长度, 操作
16, 16, 正常送
16, 32, 截取低位
32, 16, 零扩展到32位
32, 32, 正常送
CBW 字节扩展到字 不影响 AL -(有符号扩展)-> AX
CWD 字扩展到双字 不影响 AX -(有符号扩展)-> DX, AX
ADD 加法 一般规律 两个操作数不能同时是内存
ADC 带进位加法 一般规律 两个操作数不能同时是内存
INC 加1 不影响CF, 其他一般
SUB 减法 一般规律 两个操作数不能同时是内存
SBB 带借位减法 一般规律 两个操作数不能同时是内存
DEC 减1 一般规律
NEG 求补 不影响CF, 其他一般
CMP 比较 一般规律 两个操作数不能同时是内存
没有结果, 仅设置标志位
MUL/IMUL 乘法 乘积结果的高一半为0设置CF=OF=0, 否则设置CF=OF=1. 对于CF和OF以外的标志位无定义 操作数不能是立即数、段寄存器
内存操作数指定是WORD PTR 还是BYTE PTR
DIV/IDIV 除法 无定义 操作数不能是立即数、段寄存器
内存操作数指定是WORD PTR 还是BYTE PTR
AND 位与 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律
OR 位或 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律
NOT 按位求反 不影响
XOR 位异或 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律
TEST 测试 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律
SHL/SHR 逻辑左右移 见标志位设置总结 所移位数要么为1, 要么为CL
SAL/SAR 算术左右移 见标志位设置总结 所移位数要么为1, 要么为CL
ROL/ROR 循环左右移 见标志位设置总结 所移位数要么为1, 要么为CL
RCL/RCR 循环带借位左右移 见标志位设置总结 所移位数要么为1, 要么为CL
MOVS 串转移 不影响
CMPS 串比较 见标志位设置总结
LODS 从串取 不影响
STOS 存入串 不影响
SCANS 扫描串 见标志位设置总结
INS 串输入 不影响
OUTS 串输出 不影响
JMP 无条件跳转 不影响
LOOP 循环 不影响
LOOPZ/LOOPE 循环 不影响
LOOPNZ/LOOPNE 循环 不影响
J__ 有条件跳转 不影响 三类:
标志位类JZ, JS, JO, JC
无符号JA, JB
有符号JG, JL
(‘不’ : J后加N, ‘等于’ : 后面跟E)
CALL 子程序调用 不影响
RET 子程序返回 不影响

常用DOS调用

  • 1号调用

    1
    2
    3
    MOV AH, 01H
    INT 21H
    ; 控制台等待输入, 输入字符的ASCII存在AL中
  • 2号调用

    1
    2
    3
    MOV AH, 02H
    MOV DL, 'A'
    INT 21H ; 控制台输出一个字符, 其ASCII码在DL中
  • 9号调用

    1
    2
    3
    4
    ; 输出字符串, 以'$'为结尾标志, 字符串起始地址需要存在DX中
    MOV AH, 09H
    LEA DX, STRING ; STRING DB "Hello, World!$"
    INT 21H
  • 10号调用

    1
    2
    3
    4
    5
    6
    7
    8
    ; 输入字符串, 要求较复杂
    ; 定义缓冲区 : BUF DB 80, ?, 80 DUP('$')
    ; 规则 : 调用前将BUF的地址传入DX, 调用后实际字符串存到了BUF的第三个字节开始之后
    ; 最多输入字符的个数有BUF的第一个字节指定, 这也意味着10号调用一次最多可读入255个
    ; 输入字符串实际大小将被存放在BUF的第2个字节
    MOV AH, 0AH
    LEA DX, BUF
    INT 21H
  • 4CH号调用

    1
    2
    3
    ; 返回DOS, 程序结束应调用它, 否则会出运行时错误
    MOV AH, 4CH
    INT 21H ;调用4CH号功能,结束程序

汇编子程序设计

  • 函数定义

    1
    2
    3
    4
    函数名 PROC
    ; 函数体
    RET ; 函数一定有RET语句作为返回调用处的出口
    函数名 ENDP
  • 函数调用

    1
    CALL 函数名
  • 函数参数和返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ; 汇编函数没有所谓参数和返回值
    ;; 作为参数功能的寄存器, 即利用全局变量传递数据
    ; 例子:

    ; MOD函数, 参数为AX, BL, 返回值为DL, 为AX % BL的值, 均为无符号数
    MOD PROC
    DIV BL
    MOV DL, AH
    MOD ENDP
  • 避免寄存器被破坏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    ; 函数内可能会用到除了用作参数和返回值的寄存器, 
    ; 为了避免调用方的数据被破坏, 应该在函数体内对适当的寄存器进行保护, 利用PUSH,POP

    ;;;;;;;;;;;;;;;;;;;;;;;;;
    ; function : print \r\n ; ; 打印回车换行
    ;;;;;;;;;;;;;;;;;;;;;;;;;
    FUNC_PRINT_CRLF PROC
    PUSH DX ; 保护DX, AX
    PUSH AX

    ; print \r\n
    MOV DL, 0DH ; '\r'
    MOV AH, 02H
    INT 21H

    MOV DL, 0AH ; '\n'
    MOV AH, 02H
    INT 21H

    POP AX ; 恢复DX, AX
    POP DX

    ; return
    RET
    FUNC_PRINT_CRLF ENDP

汇编常用技法

  • 灵活使用栈(PUSH & POP)

    • 暂存(保护)寄存器的值
    • 逆序数组
    • 加个特殊标记当栈底(我常用-1(0FFFFH)做栈底)
  • 字符串加个特殊标记当结尾(常用’$’)

汇编编程实战

输入输出

0.输入以回车结束的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; function : input a string into addr-BX, ;
; whose length must be less ;
; than CX ;
; params : BX, addr; CX, max-length ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FUNC_INPUT_STRING_TO_BX PROC
PUSH AX
PUSH SI

MOV SI, 0
FISTB_BEGIN:
; 如果长度大于CX, 直接结束
CMP SI, CX
JE FISTB_END2

; 从键盘获取一个ASCII, 存放在AL
MOV AH, 1
INT 21H
CMP AL, 0DH
JE FISTB_END ; 遇到回车直接跳出

MOV [BX][SI], AL ; 存放到..
INC SI

JMP FISTB_BEGIN
FISTB_END:
; End of loop of inputing & saving
MOV AL, '$'
MOV [BX][SI], AL ; 字符串以'$'结尾
FISTB_END2:

POP SI
POP AX
RET
FUNC_INPUT_STRING_TO_BX ENDP

1.输出有结束标志的字符串

结尾为 \'$\'
1
2
3
4
5
; 结尾 : '$'

LEA DX, 字符串名
MOV AH, 09H
INT 21H

2.输出N进制数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; function : print CX base 10 ; ; 打印CX的值以10进制无符号
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FUNC_PRINT_CS_BASE10 PROC
PUSH AX
PUSH DX

MOV AX, CX
MOV DX, -1
PUSH DX ; -1为栈底标志

; 除10取余获取各位数字并压栈
FUNC_PRINT_CS_BASE10_GET_NUMBER_BIT_LOOP_BEGIN:

CBW ; 将上一步的商扩展到字以作为这一步的被除数

MOV DL, 10
DIV DL
PUSH AX

CMP AL, 0
JNE FUNC_PRINT_CS_BASE10_GET_NUMBER_BIT_LOOP_BEGIN

; 出栈并打印
FUNC_PRINT_CS_BASE10_PRINT_BIT_BY_BIT_BEGIN:
POP AX
CMP AX, -1
JE FUNC_PRINT_CS_BASE10_PRINT_BIT_BY_BIT_END

; 打印一位数字
ADD AH, 30H ; bitNumber + '0'
MOV DL, AH

;;;;;;;;;;;;; ; 打印DL
MOV AH, 02H
INT 21H
;;;;;;;;;;;;;

JMP FUNC_PRINT_CS_BASE10_PRINT_BIT_BY_BIT_BEGIN
FUNC_PRINT_CS_BASE10_PRINT_BIT_BY_BIT_END:

POP DX
POP AX
RET
FUNC_PRINT_CS_BASE10 ENDP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; function : print CX base 16 ; ; 打印CX的值以16进制无符号
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FUNC_PRINT_CS_BASE16 PROC
PUSH AX
PUSH DX

MOV AX, CX
MOV DX, -1
PUSH DX ; -1为栈底标志

; 除10取余获取各位数字并压栈
FUNC_PRINT_CS_BASE16_GET_NUMBER_BIT_LOOP_BEGIN:

CBW ; 将上一步的商扩展到字以作为这一步的被除数

MOV DL, 16
DIV DL
PUSH AX

CMP AL, 0
JNE FUNC_PRINT_CS_BASE16_GET_NUMBER_BIT_LOOP_BEGIN

; 出栈并打印
FUNC_PRINT_CS_BASE16_PRINT_BIT_BY_BIT_BEGIN:
POP AX
CMP AX, -1
JE FUNC_PRINT_CS_BASE16_PRINT_BIT_BY_BIT_END

; 打印一位数字
CMP AH, 9
JNA FUNC_PRINT_CS_BASE16_PRINT_BIT_LESS10
ADD AH, 7
FUNC_PRINT_CS_BASE16_PRINT_BIT_LESS10:
ADD AH, 30H ; bitNumber + '0'
MOV DL, AH

;;;;;;;;;;;;; ; 打印DL
MOV AH, 02H
INT 21H
;;;;;;;;;;;;;

JMP FUNC_PRINT_CS_BASE16_PRINT_BIT_BY_BIT_BEGIN
FUNC_PRINT_CS_BASE16_PRINT_BIT_BY_BIT_END:

POP DX
POP AX
RET
FUNC_PRINT_CS_BASE16 ENDP

;#######################################################################;
; 打印16位无符号数(比上面的更好, 上面的打印比较大的数会出BUG)
DATA SEGMENT
VAL DW 65535
DATA ENDS

STACK SEGMENT
STACK ENDS

CODE SEGMENT
ASSUME DS: DATA, CS: CODE, SS: STACK
START:
MOV AX, DATA
MOV DS, AX

MOV AX, VAL
MOV DX, -1
PUSH DX
GET_NUMBER_BIT_LOOP_BEGIN:
MOV DX, 0

MOV BX, 10
DIV BX
PUSH DX

CMP AX, 0
JNE GET_NUMBER_BIT_LOOP_BEGIN

PRINT_LOOP_BEGIN:
POP DX
CMP DX, -1
JE PRINT_LOOP_END

ADD DL, 30H
MOV AH, 02H
INT 21H

JMP PRINT_LOOP_BEGIN
PRINT_LOOP_END:

MOV AX, 4C00H
INT 21H

CODE ENDS
END START

3.输入N进制数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
; 10进制为例
; 其他进制注意除数, 字母的处理(A-F)
MOV BX, 0
INPUT_BEGIN:
MOV AH, 01H
INT 21H ; 输入字符到AL

CMP AL, 0DH
JE INPUT_END ; 回车, 结束

SUB AL, 30H ; AL <- AL - '0', 获取字符对应的数字(0-9)
PUSH AX ; 保护AX
MOV AX, 10
MUL BX ; AX <- BX * 10
MOV BX, AX ; 将乘法结果存回BX, 即相当于 : BX <- BX * 10
POP AX ; 恢复AX
CBW ; 扩展AL -> AX, 以便于将AL(最新输入的数位)累加到BX

ADD BX, AX ; 此条及以上 : BX <- BX * 10 + AL
JMP INPUT_BEGIN ; next-loop
INPUT_END:

数组

0.找出数组中的最大值

新增代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此
ARR DW 45H, 32H, 12H, 54H, 2H, 23H, 12H, 11H ; 数组
ARR_LEN DW 8 ; 数组大小
DATA ENDS ; ENDS : END of SEGMENT

CODE SEGMENT ; 代码段
ASSUME DS: DATA, CS: CODE ; 声明DATA段是DS段, CODE段是CS段
START: ; 程序从这里开始执行
MOV AX, DATA
MOV DS, AX

; [1]写自己的代码, 要求找出ARR数组中的最大值, 将结果存在AX中
MOV AX, ARR[0]
MOV SI, 2
LOOP_BEGIN:
CMP SI, ARR_LEN
JAE LOOP_END

CMP AX, ARR[SI]
JAE NEXT_LOOP

MOV AX, ARR[SI]

NEXT_LOOP:
ADD SI, 2
JMP LOOP_BEGIN

LOOP_END:

; 调用DOS中断返回DOS
MOV AH, 4CH
INT 21H
CODE ENDS
END START

1.插入元素到升序序列

新增代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此
ARR DW 10H, 20H, 30H, 43H, ? ; 数组
DATA ENDS ; ENDS : END of SEGMENT

CODE SEGMENT ; 代码段
ASSUME DS: DATA, CS: CODE ; 声明DATA段是DS段, CODE段是CS段
START: ; 程序从这里开始执行
MOV AX, DATA
MOV DS, AX

MOV CX, 90H
; [2]写自己的代码, 要求将CX中的数字插入ARR中, 使得ARR仍然保持升序
MOV SI, 8
LOOP_BEGIN:
CMP CX, ARR[SI - 2]
JA LOOP_END
MOV DX, ARR[SI - 2]
MOV ARR[SI], DX
SUB SI, 2
CMP SI, 0
JE LOOP_END
JMP LOOP_BEGIN

LOOP_END:
MOV ARR[SI], CX

; 调用DOS中断返回DOS
MOV AH, 4CH
INT 21H
CODE ENDS
END START

2.排序数组(冒泡排序)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
DATA SEGMENT
ARR DW 45H, 32H, 12H, 54H, 2H, 23H, 12H, 11H
ARR_LEN DW 8
DATA ENDS

CODE SEGMENT
ASSUME DS: DATA, CS: CODE
START:
MOV AX, DATA
MOV DS, AX

; 将ARR升序排列
MOV CX, ARR_LEN ; 数组大小为8 ; 外循环采用CX计数
DEC CX
MOV DX, CX ; 内循环采用DX进行计数
OUTER_LOOP_BEGIN:

MOV SI, 0
MOV DX, ARR_LEN
DEC DX
INNER_LOOP_BEGIN:

; if arr[SI] > arr[SI + 2] : swap
MOV BX, ARR[SI]
CMP BX, ARR[SI + 2]
JNA INNER_LOOP_CONTINUE ; else continue

XCHG BX, ARR[SI + 2] ; swap
MOV ARR[SI], BX
INNER_LOOP_CONTINUE:

ADD SI, 2 ; 数组元素为DW, 注意SI每次加2
DEC DX
CMP DX, 0
JNZ INNER_LOOP_BEGIN
LOOP OUTER_LOOP_BEGIN

MOV AH, 4CH
INT 21H
CODE ENDS
END START

字符串

0.比较字符串是否相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; function : compare string a and string b ;
; compare-max-len is in CX ;
; params : CX, maxlen; ; ; 比较两个字符串,
; AX, a's addr; ; ; 它们的起始地址在AX, BX里
; BX, b's addr ; ; 要求字符串均已'$'结尾
; return : DL, 0 if ==, else 1 ; ; CX被用来指定比较最大长度
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FUNC_CMP_STRING_A_AND_B PROC
PUSH BP
PUSH SI
PUSH DX

MOV BP, AX
MOV SI, 0
FCSAAB_LOOP_BEGIN:

MOV DH, DS:[BX][SI]
CMP DS:[BP][SI], DH ; CMP不可直接比较两块内存
JNE FCSAAB_LOOP_END_NE

CMP DH, '$'
JE FCSAAB_LOOP_END_E

INC SI
LOOP FCSAAB_LOOP_BEGIN
FCSAAB_LOOP_END_E:
POP DX
MOV DL, 0
POP SI
POP BP
RET
FCSAAB_LOOP_END_NE:
POP DX
MOV DL, 1
POP SI
POP BP
RET
FUNC_CMP_STRING_A_AND_B ENDP

1.翻转字符串(同样适用于数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
DATA SEGMENT
S DB "Hello World!!"
S_LEN EQU 13
DATA ENDS

CODE SEGMENT
ASSUME DS: DATA, CS: CODE
START:
MOV AX, DATA
MOV DS, AX

; 思路, 两个下标分别从头向后和从尾向前, 每次循环将两者交换, if SI >= DI : break
MOV SI, 0 ; 从头向后的下标
MOV DI, S_LEN - 1 ; 从尾向前的下标

REVERSE_LOOP_BEGIN:

; swap
MOV CL, S[SI] ; 注意数据类型匹配
XCHG CL, S[DI]
MOV S[SI], CL

INC SI
DEC DI
CMP SI, DI
JAE REVERSE_LOOP_END ; if SI >= DI : break
JMP REVERSE_LOOP_BEGIN ; else continue
REVERSE_LOOP_END:

MOV AH, 4CH
INT 21H
CODE ENDS
END START

综合示例

0.输入字符串找出出现数量最多的字符输出该字符及其数量

新增代码示例

(见期末模拟(一), 参考程序随答案公布)

  • 输入格式 : 输入一行字符串, 以回车结尾, 输入字符个数不会多于200个
  • 输出格式 : 输出两行, 每一行均以回车换行结尾, 第一行为出现最多的字符, 第二行为出现的次数
  • (最多出现的字符由多个时, 随意输出一个即可)
  • 思路 : 内外两层循环,当外循环找到一个ASCII不为0的字符时进入内循环否则一直向后找,内循环统计和外循环找到的不为0的字符相同的字符个数,最后加上外循环找到的那一个,和CNT变量比大小,若大于CNT则更新CNT为新的计数,更新CHAR为新的最多出字符,为了避免重复,统计完即刻将当前位置字符赋为ASCII 0,将外循环那个字符也赋成ASCII 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此
CNT DB 0
CHAR DB 0
HELPER DB 200, ?
STR DB 200 DUP('$')
DATA ENDS ; ENDS : END of SEGMENT

CODE SEGMENT ; 代码段
ASSUME DS: DATA, CS: CODE ; 声明DATA段是DS段, CODE段是CS段
START: ; 程序从这里开始执行
MOV AX, DATA
MOV DS, AX

LEA DX, HELPER
MOV AH, 0AH
INT 21H

MOV SI, 0
OUTER_LOOP:
MOV AL, HELPER[1]
MOV AH, 0
CMP SI, AX
JAE OUTER_LOOP_END

MOV DL, STR[SI]
CMP DL, 0

JNE OUTER_NOT_0
INC SI
JMP OUTER_LOOP

OUTER_NOT_0:
MOV CL, 1
MOV DI, SI
INC DI

INNER_LOOP:
MOV AL, HELPER[1]
MOV AH, 0
CMP DI, AX
JAE OUTER_NEXT_LOOP

MOV DL, STR[SI]
CMP DL, STR[DI]
JNE INNER_NEXT_LOOP

INC CL
MOV STR[DI], 0

INNER_NEXT_LOOP:
INC DI
JMP INNER_LOOP

OUTER_NEXT_LOOP:
CMP CL, CNT
JBE OUTER_NEXT_LOOP2

MOV CNT, CL
MOV CHAR, DL

OUTER_NEXT_LOOP2:
MOV STR[SI], 0
INC SI
JMP OUTER_LOOP

OUTER_LOOP_END:
MOV DL, CHAR
MOV AH, 02H
INT 21H

MOV AL, CNT
MOV AH, 0

; 以十进制打印AX
MOV DX, -1
PUSH DX ; -1为栈底标志

; 除10取余获取各位数字并压栈
GET_NUMBER_BIT_LOOP_BEGIN:

MOV AH, 0 ; 将上一步的商扩展到字以作为这一步的被除数

MOV DL, 10
DIV DL
PUSH AX

CMP AL, 0
JNE GET_NUMBER_BIT_LOOP_BEGIN

; 出栈并打印
PRINT_BIT_BY_BIT_BEGIN:
POP AX
CMP AX, -1
JE PRINT_BIT_BY_BIT_END

; 打印一位数字
ADD AH, 30H ; bitNumber + '0'
MOV DL, AH

MOV AH, 02H
INT 21H

JMP PRINT_BIT_BY_BIT_BEGIN
PRINT_BIT_BY_BIT_END:

MOV AH, 4CH
INT 21H
CODE ENDS
END START

1.输入若干数字求平均值, 最大值, 最小值并输出

  • 思路
    • 边输入边求和并计数以及记录最大最小值
    • 输出

2.输入字符串反向输出

  • 格式 : 输入一行字符串, 要求倒序输出, 末尾要求打印回车换行
  • 思路
    • 边输入边压栈
    • 边弹栈边输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
DATA SEGMENT
DATA ENDS

CODE SEGMENT
ASSUME DS: DATA, CS: CODE
START:
MOV AX, DATA
MOV DS, AX

MOV DX, -1
PUSh DX ; 以0FFFFH为栈底标志

INPUT_LOOP_BEGIN: ; 边输入边压栈

MOV AH, 01H
INT 21H

CMP AL, 0DH
JE INPUT_LOOP_END

PUSH AX ; 入栈
JMP INPUT_LOOP_BEGIN
INPUT_LOOP_END:

OUTPUT_LOOP_BEGIN: ; 边弹栈边输出
POP DX
CMP DX, -1
JE OUTPUT_LOOP_END

MOV AH, 02H
INT 21H ; print DL
JMP OUTPUT_LOOP_BEGIN
OUTPUT_LOOP_END:

; print \r\n
MOV DL, 0DH ; '\r'
MOV AH, 02H
INT 21H

MOV DL, 0AH ; '\n'
MOV AH, 02H
INT 21H

MOV AH, 4CH
INT 21H
CODE ENDS
END START

3.输入N求斐波那契数列第N项并输出

  • 思路 : 三个变量(寄存器)来回倒腾

4.判断输入括号形式是否合法[拓展, 栈的应用]