8086汇编-子程序设计
9unk Lv5

子程序设计

当某个程序片段需要反复使用、或是具有通用性可以在多个程序中使用,我们就需要把这段代码设计为子程序。这样能用小缩短程序地长度,节约存储空间,减少程序设计地工作量。此外当某个程序片段地功能相对独立时,可以把它设计成子程序,这样便于模块化,也比那与程序地阅读、调试和修改。

过程调用和返回指令

  1. 过程调用(call)指令和过程返回(ret)指令属于程序控制指令。通常,过程调用指令用于由主程序转子程序,过程返回指令用于由子程序返回主程序。
  2. 过程调用指令,可以像无条件跳转指令一样进行跳转,且过程调用指令有段内调用和段间调用之分。与之相对应的。过程返回指令也有段内跳转和段间跳转。
  3. 段内调用和段内返回称为近调用和近返回,段间调用和段间返回称为远调用和远返回
  4. 在汇编语言中,过程也有远近之分。

过程调用指令

  1. 过程调用指令,首先把子程序的返回地址压入堆栈,以便执行完子程序后返回调用程序(主程序)继续往下执行。
  2. 按照转移目标是否是同一段来分,地址用指令分为段内调用和段间调用。
  3. 按照获得转移目标地址的方式来分,调用指令分为直接调用和间接调用。
  4. 过程调用指令不影响标志位。

段内直接调用

段内直接调用指令用于调用当前段内的子程序,格式如下:
call 过程名(标号)

例如:
call sub1

这条指令相当于

1
2
push "call sub1的下一条指令的偏移地址"
jmp sub1

具体的操作分解如下:

1
2
3
sp <= sp-2  ;提高栈顶
[sp] <= IP ;call xxx的下一条指令的偏移地址。
IP <= IP+disp
  1. call指令所在的偏移地址是,子程序的起始地址;
  2. call 下面一条指令的偏移地址是,子程序的结束地址;
  3. disp=结束地址-起始地址。
  4. 段内直接调用指令中,是以一个字表示disp,所以转移范围在 -32768~+32767 之间。

总结验证

起始地址:076B:0008
结束地址:076B:000B
disp=3

图片

图片

从上图中可以看到,call 指令执行前 sp=0000,指令执行之后 sp=sp-2=FFFE。内存中存储偏移地址 000B=起始IP + disp=0008+3。还需要注意一下,这里 IP寄存器 修改成了 0016

段内间接转移

段内间接转移格式如下:
call OPRD

例如:

1
2
3
call BX
call word ptr [bx]
call varw ;varw 字变量

OPRD 是16位通用寄存器或字存储器操作数,具体操作分解如下:

1
2
3
sp <= sp-2
[sp] <= IP
IP <= OPRD

总结验证
图片

段间直接调用

段间直接调用,用于调用其他代码段中的子程序。格式如下:
call 过程名(标号)

例如:

1
2
call far ptr SUBRO
call SUBF

该指令把返回地址的段值压入堆栈,再把返回地址的偏移压入堆栈,达到保存返回地址的目的。

具体操作分解如下:

1
2
3
4
5
6
sp <= sp-2
[sp] <= cs
sp <= sp-2
[sp] <= IP
IP <= 子程序的偏移地址
CS <= 子程序的代码段地址

总结验证
图片

可以看到,远跳转的 call 指令,后面的地址是 “CS:IP”。堆栈中存放的也是 “CS:IP”

段间间接调用

段间间接调用指令也用于调用其他代码段中的子程序。格式如下:
call OPRD
OPRD 是双字存储器操作数

具体操作分解如下:

1
2
3
4
5
6
sp <= sp-2
[sp] <= cs
sp <= sp-2
[sp] <= IP
IP <= OPRD 的低字
CS <= OPRD 的高字

例如:

1
2
call DWORD PTR [bx]
call VARD

总结验证
图片

可以看到,变量类型只要是 dd 就行,当编译程序时会自动加上 far 属性。

过程返回指令

过程返回指令把子程序的返回地址从堆栈弹出到 IPCS:IP,从而返回到主程序继续我往下执行。过程返回指令不影响标志位。

段内返回指令

指令格式如下:

1
ret

该指令完成的具体操作如下所示:

1
2
IP <= [SP]
SP <= SP+2

总结验证
图片

段间返回指令

指令格式如下:

1
ret

该指令完成的具体操作如下所示:

1
2
3
4
IP <= [SP]
SP <= SP+2
CS <= [SP]
SP <= SP+2

总结验证
图片

编译器自动把 RET 编译成另一个段间返回指令 RETF 。

段间返回指令 RETF

图片
图片

可以看到我们使用段内 call 指令调用子程序,但使用的是 RETF 返回,导致程序返回出错。

无论 RETF 出现在远过程还是近过程中,编译器总是会把它编译成段间返回指令。

返回指令+立即数

指令格式如下:
ret 表达式

汇编程序会把表达式的结果取整。

该指令会先弹出 “一个字” 或是 “双字” 作为返回地址,再根据 data 修改堆栈指针。

图片

可以看到上面 ret 4 是吧 SP 寄存器改为了 4

过程定义语句

过程定义语句(子程序定义语句),可把子程序起名,并且定义近类型或远类型。过程定义语句的格式如下:

1
2
3
4
5
过程名 PROC [NEAR | FAR]
.
.
.
过程名 ENDP

过程定义语句中,过程名必须要一致。现阶段程序的代码比较少,基本都使用 NEAR 属性。后面程序的代码多的时候,使用 FAR 属性会比较方便。

范例

把一个十六进制数转换为对应 ASCII 码

程序逻辑结构图

HTOASC

代码

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
;程序名:HTOASC.asm
;功能:实现把一个十六进制数转换为对应的 ASCII 码的程序。
;方法一:分开计算,数字+30h,字母+37h
assume ds:data,cs:code

data segment
number db 0Fh
data ends

code segment
start:
mov ax,data
mov ds,ax
;
mov al,number
call HTOASC
mov dl,al
mov ah,2
int 21h
mov ax,4c00h
int 21h


;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
pushf
and al,0fh
cmp al,0ah
jb num_add
add al,37h
jmp return
num_add:
add al,30h
return:
popf
ret
HTOASC endp
code ends
end start

子程序举例

例1

把用ASCII码表示的两位十进制数,转换为对应的十进制数的子程序。设X为十位数,Y为个位数

程序流程图

T4-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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
;程序名:T4-1.asm
;作者:啥都不会
;创建日期:2022/3/14
;修改日期: 修改者:
;子程序名:subr
;程序的描述:把用ASCII码表示的两位十进制数,转换为对应的十进制数的子程序。设X为十位数,Y为个位数
;算法:10X+y
;=============================================================================================

assume cs:code,ds:data

data segment
num db 31h,30h
val db ?
data ends

code segment
start:
mov ax,data
mov ds,ax
;
mov dh,num[0]
mov dl,num[1]
call subr
mov val,al
stop:
mov ax,4c00h
int 21h
;-----------------------------------------------------
subr PROC
;
; 功能:两位ASCII码组成的十进制数,转换成十进制数
; 传参方式:寄存器传参
; 入口参数:dh高位、dl低位
; 出口参数:al
;-----------------------------------------------------
mov al,dh
and al,0fh
mov ah,10
mul ah
mov ah,dl
and ah,0fh
add al,ah
ret
subr endp
code ends
end start

例2

程序流程图

  1. 2-1
    写一个把一个字大小的16进制数,转换成4个ASCII码的子程序
    T4-2

  2. 2-2
    利用子程序 HTASCS 按十六进制数形式显示地址为 F000:0000H 的字单元内容
    T4-2 - 2

代码

  1. T4-2-1.asm

    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
    ;程序名:T4-2-1.asm
    ;作者:啥都不会
    ;创建日期:2022/3/14
    ;修改日期: 修改者:
    ;子程序名:htascs
    ;程序的描述:写一个把一个字大小的16进制数,转换成4个ASCII码
    ;算法:把16进制数向左循环移位4次,使高四位变为低四位,
    ;析出低四位调用子程序 htoasc 转换 1 位十六进制 ASCII 码,循环四次
    ;优化:直接循环输出结果
    ;=============================================================================================
    assume cs:code,ds:data

    data segment
    num dw 1234h
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov bx,num
    call htascs
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    htascs PROC
    ;
    ; 功能:把一个字大小的16进制数,转换成4个ASCII码
    ; 传参方式:寄存器传参
    ; 入口参数:BX数值
    ; 出口参数:无
    ; 说 明:调用子函数 htascs
    ;-----------------------------------------------------
    mov cx,4
    htascs1:
    ROL BX,1
    ROL BX,1
    ROL BX,1
    ROL BX,1
    mov al,bl
    call HTOASC
    mov dl,al
    mov ah,2
    int 21h
    loop htascs1
    ret
    htascs endp

    ;-----------------------------------------------------
    HTOASC PROC NEAR
    ;
    ; 功能:把一个十六进制数转换为对应的 ASCII 码
    ; 传参方式:寄存器传参
    ; 入口参数:al
    ; 出口参数:al
    ;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp return
    num_add:
    add al,30h
    return:
    popf
    ret
    HTOASC endp
    code ends
    end start
  2. T4-2-2.asm

    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
    ;程序名:T4-2-2.asm
    ;利用子程序 HTASCS 按十六进制数形式显示地址为 F000:0000H 的字单元内容
    assume cs:code

    code segment
    start:
    mov ax,0f000h
    mov ds,ax
    ;
    mov bx,[si]
    call HTASCS
    mov ax,4c00h
    int 21h


    ;-----------------------------------------------------
    HTASCS PROC
    ;
    ; 功能:把一个字大小的16进制数,转换成4个ASCII码
    ; 传参方式:寄存器传参
    ; 入口参数:dx
    ; 出口参数:DS:BX存储所得ASCII码串的缓冲区首地址
    ;-----------------------------------------------------
    mov cx,4
    HTASCS1:
    ROL BX,1
    ROL BX,1
    ROL BX,1
    ROL BX,1
    mov al,dl
    call HTOASC
    mov dl,al
    mov ah,2
    int 21h
    loop HTASCS1
    ret
    HTASCS endp

    ;-----------------------------------------------------
    HTOASC PROC NEAR
    ;
    ; 功能:把一个十六进制数转换为对应的 ASCII 码
    ; 传参方式:寄存器传参
    ; 入口参数:al
    ; 出口参数:al
    ;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp pop_stack
    num_add:
    add al,30h
    pop_stack:
    popf
    ret
    HTOASC endp
    code ends
    end start

例3

程序流程图

  1. T4-3-1
    把T3-11.asm改写成子程序,写一个把16位二进制数转换为5位十进制数ASCII码的子程序,为了简单,设二进制数为无符号数。

    例3的逻辑结构和 T3-11-2 是一样的,这里就不再画了

  2. T4-3-2
    把8位二进制数转换为2位十六进制数的 ASCII 码
    T4-3-2

代码

  1. T4-3-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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    ;程序名:T4-3-1.asm
    ;功能:写一个把16位二进制数转换为5位十进制数ASCII码的子程序,为了简单二进制数为无符号数。把T3-11.asm改写成子程序。

    assume cs:code,ds:data

    data segment
    number dw 0AECFh
    result db 0,0,0,0,0,'$'
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,number
    lea si,result
    call BTOASC
    lea dx,[si]
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    BTOASC PROC
    ;
    ; 功能:把16位二进制数转换为5位十进制数
    ; 传参方式:寄存器传参
    ; 入口参数:AX=要转换的值、SI=存储结果的缓冲区首地址
    ; 出口参数:DS:SI存储所得ASCII码串的缓冲区首地址
    ;-----------------------------------------------------
    mov di,5
    mov cx,10
    L1:
    xor dx,dx
    div cx
    add dl,30h
    mov result[di-1],dl ;用di判断循环,但是存储结果di需要减1
    dec di
    jnz L1
    ;函数返回
    ret
    BTOASC endp
    code ends
    end start
  2. T4-3-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
    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
    ;程序名:T4-3-2.asm
    ;功能:把8位二进制数转换为2位十六进制数的 ASCII 码
    assume cs:code,ds:data
    data segment
    char db 0abh
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov al,char
    call WHTOASC
    ;
    mov cx,2
    mov dx,ax
    L1:
    xchg dh,dl
    mov ah,2
    int 21h
    loop L1
    ;
    mov ax,4c00h
    int 21h
    ;-----------------------------------------------------
    WHTOASC PROC
    ;
    ; 功能:把8位二进制数转换为2位 ASCII 码
    ; 传参方式:寄存器传参
    ; 入口参数:AL=要转换的值
    ; 出口参数:AH = 十六进制数高位的 ASCII 码
    ; AL = 十六进制数低位的 ASCII 码
    ;其他说明:(1)近过程
    ; (2)除 AX 寄存器外,不影响其他寄存器
    ; (3)调用HTOASC 实现十六进制数到ASCII码的转换
    ;-----------------------------------------------------
    mov ah,al
    shr al,1
    shr al,1
    shr al,1
    shr al,1
    call HTOASC
    xchg ah,al
    call HTOASC
    ret
    WHTOASC endp


    ;-----------------------------------------------------
    HTOASC PROC NEAR
    ;
    ; 功能:把一位十六进制数转换为对应的 ASCII 码
    ; 传参方式:寄存器传参
    ; 入口参数:al
    ; 出口参数:al
    ;-----------------------------------------------------
    pushf
    and al,0fh
    cmp al,0ah
    jb num_add
    add al,37h
    jmp pop_stack
    num_add:
    add al,30h
    pop_stack:
    popf
    ret
    HTOASC endp

    code ends
    end start

寄存器的保护与恢复

子程序为了完成其功能,通常需要使用一些寄存器或存储单元存放内容。也就是说,子程序运行时通常会破坏寄存器的数据。所以在调用子程序时,我们还需要将,可能破坏的寄存器数据进行保护与恢复。

寄存器保护与恢复的方法:
在子程序一开始就把子程序中要改变的寄存器内容压入堆栈,在返回之前再恢复这些寄存器的内容。如下图所示:
图片

同时还要注意:子程序运行时,会影响到标志寄存器。因此有时我们还需要保护标志寄存器。指令如下:

pushf(保护标志寄存器)
popf(恢复标志寄存器)

主程序与子程序之间的参数传递

主程序在调用子程序时,通常需要向子程序传递参数(类似函数的参数);同样,子程序运行后也经常要把一些结果参数传给主程序(类似函数的返回值)。主程序与子程序之间的这种信息传递称为参数传递。

  1. 由主程序传递给子程序的参数称为子程序的入口参数
  2. 由子程序传给主程序的参数称为子程序的出口参数
  3. 子程序可以有入口参数,有出口参数;有入口参数,无出口参数;无入口参数,有出口参数

参数传递的方法分别有:寄存器传递法、内存单元传递法、堆栈传递法和call 后续区传递法等。

说到底,只要子程序能定位到参数在哪,就可以用这种方法传递参数。以上这些方法的区别在于,使用寄存器传递法的子程序,比使用其他参数方法的速度要快。

寄存器参数传递法

利用寄存器传递参数就是把参数放在约定的寄存器中。
优点:实现简单、调用方便。
缺点:因为寄存器个数有限,且寄存器还要存放其他数据,所以只适用于传递参数较少的情况

例1

程序流程图

  1. T4-4
    写一个 把大写字母改为小写字母的子程序。
    T4-4

  2. T4-4-1
    写一个把大写字母的字符串更改为小写字母的子程序。
    T4-4-1

代码

  1. T4-4.asm

    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
    ;程序名:T4-5.asm
    ;功能:写一个把一个大写字母更改为小写字母的子程序。
    ;子程序名:UPTOLW
    assume cs:code,ds:data

    data segment
    char db 'H'
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov al,char
    call UPTOLW
    ;
    mov dl,al
    mov ah,2
    int 21h
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    UPTOLW PROC
    ;
    ; 功能:把一个大写字母更改为小写字母,其他字符不变
    ; 传参方式:寄存器传参
    ; 入口参数:AL=字符ASCII码
    ; 出口参数:AL=字符ASCII码
    ;-----------------------------------------------------
    pushf
    cmp al,'A'
    jb UPTOLW1
    cmp al,'Z'
    JA UPTOLW1
    add al,'a'-'A'
    ;
    UPTOLW1:
    popf
    ret
    UPTOLW endp
    code ends
    end start
  2. T4-4-1.asm

    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
    ;程序名:T4-4-1.asm
    ;功能:写一个把大写字母的字符串更改为小写字母的子程序。
    ;子程序名:UPTOLWS
    assume cs:code,ds:data

    data segment
    char db 'HELLO',0,0dh,0ah,'$'
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    lea si,char
    call UPTOLWS
    ;
    mov dx,si
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    UPTOLWS PROC
    ;
    ; 功能:把一串大写字母更改为小写字母,其他字符不变
    ; 传参方式:寄存器传参
    ; 入口参数:SI=要转换字符的首地址
    ; 出口参数:DS:SI存储所得ASCII码串的缓冲区首地址
    ;-----------------------------------------------------
    pushf
    push ax
    push cx
    push si
    ;
    xor cx,cx
    L1:
    inc si
    inc cx
    mov al,byte ptr [si-1]
    cmp al,0
    jz UPTOLW_POP
    cmp al,'A'
    jb UPTOLW_POP
    cmp al,'Z'
    ja UPTOLW_POP
    ;
    add al,20h
    mov byte ptr [si-1],al
    jmp L1

    UPTOLW_POP:
    pop si
    pop cx
    pop ax
    popf
    ;
    ret
    UPTOLWS endp
    code ends
    end start

例2

写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。

程序流程图

T4-6

代码

  1. T4-5

    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
    ;程序名:T4-5.asm
    ;功能:写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
    ;子程序名:ISDECM
    ;处理方法:循环处理字符并复制到缓冲区中。缺点:多了复制字符的步骤。优点:准确性高
    assume cs:code,ds:data
    data segment
    string db 'H1E2L3L4L5O',0
    count dw $ - 1 ;当前的偏移地址($)-1:表示字符串 string 的长度。
    result db 10 dup(0),0dh,0ah,'$'
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov cx,count
    L1:
    mov al,string[si]
    call ISDECM
    inc si ;inc 指令不影响 CF 位,所以不影响程序运行结果
    jc L2
    loop L1
    jmp stop
    L2:
    mov result[di],al ;di 寄存器的默认值是0,且默认使用 ds 段寄存器
    inc di
    cmp cx,0
    jz stop
    ;dec cx
    ;jnz L1
    loop L1
    stop:
    lea dx,result
    mov ah,9
    int 21h ;输出结果
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    ISDECM PROC
    ;
    ; 功能:判别一个字符是否为数字
    ; 传参方式:寄存器传参
    ; 入口参数:AL=字符
    ; 出口参数:CF为0表示是数字符,否则字符是非数字符
    ;-----------------------------------------------------
    cmp al,'0'
    jb ISDECM1
    cmp al,'9'
    ja ISDECM1
    CLC
    ret
    ISDECM1:
    STC
    ret
    ISDECM endp
    code ends
    end start
  2. 优化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
    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
    ;程序名:T4-5-1.asm
    ;功能:写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
    ;子程序名:ISDECM
    ;处理方法:循环处理字符,并替换原有字符。缺点:删除数字后,原有字符串与现有字符串宽度不一样,容易出错。优点:代码简洁了一点点。
    assume cs:code,ds:data

    data segment
    string db 'AB=C950=asd',0,0dh,0ah,'$'
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov si,offset string
    mov di,si
    next:
    mov al,[si]
    inc si
    or al,al
    jz ok
    call ISDECM
    jnc NEXT
    mov [di],al
    inc di
    jmp next
    ok:
    mov [di],al
    ;
    lea dx,string
    mov ah,9
    int 21h
    ;
    mov ax,4c00h
    int 21h


    ;-----------------------------------------------------
    ISDECM PROC
    ;
    ; 功能:判别一个字符是否为数字
    ; 传参方式:寄存器传参
    ; 入口参数:AL=字符
    ; 出口参数:CF为0表示是数字符,否则字符是非数字符
    ;-----------------------------------------------------
    cmp al,'0'
    jb ISDECM1
    cmp al,'9'
    ja ISDECM1
    CLC
    ret
    ISDECM1:
    STC
    ret
    ISDECM endp
    code ends
    end start
  3. 优化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
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
;程序名:T4-5-2.asm
;功能:写一个删除字符串中所有非字母字符,除了空格。
;子程序名:ISDECMS
assume cs:code,ds:data

data segment
string db 'H!@EL36L0O asm',0
result db 14 dup(0),0dh,0ah,'$'
data ends

code segment
start:
mov ax,data
mov ds,ax
;
lea si,string
lea di,result
call ONLYHCARS
;
lea dx,result
mov ah,9
int 21h
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
ONLYHCARS PROC
;
; 功能:删除字符串中非字母的字符
; 传参方式:寄存器传参
; 入口参数:si=字符串首地址,di-存放处理好的字符串首地址
; 出口参数:di-处理好的字符串首地址
;-----------------------------------------------------
pushf
push bx
push ax
push si

xor bx,bx
ONLYHCARS0:
mov al,byte ptr [si]
inc si
cmp al,20h
jz charmove
;
cmp al,0
jz LYHC_POP
;
cmp al,'A'
ja ONLYHCARS1
cmp al,'a'
ja ONLYHCARS2
jmp ONLYHCARS0
ONLYHCARS1:
cmp al,'Z'
jb charmove
ONLYHCARS2:
cmp al,'z'
jb charmove
jmp ONLYHCARS0
charmove:
mov [di],al
inc di
inc bx
jmp ONLYHCARS0
sub di,bx
LYHC_POP:
pop si
pop ax
pop bx
popf
ret
ONLYHCARS endp
code ends
end start

利用存储单元传递参数

在参数较多的情况下,可利用内存变量来传递参数。
优点:子程序要处理的数据或送出的结果都是独立的存储单元,编写子程序时不容易出错。
缺点:占用了一定的存储单元,通用性较差。
解决方法:把参数组织成一张参数表,存放在某个存储区,然后再把存储区首地址传送给子程序。

在当时内存空间是非常少的,因此当时的开发人员都非常注重内存空间的使用,尽量少使用内存。

例1

写一个实现32位数相加的子程序

程序流程图

  1. T4-6
    T4-7-1

  2. T4-6-1
    T4-7

代码

  1. T4-6

    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
    ;程序名:T4-6.asm
    ;功能:实现32位数相加
    ;子程序名:MADD
    ;算法:使用顺序结构

    assume cs:code,ds:data

    data segment
    DATA1 dd 0ABCDABCDh
    DATA2 dd 0CDEFCDEFh
    DATA3 dw 0,0,0,0
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    call MADD
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    MADD PROC
    ; 功能:32位数相加
    ; 传参方式:存储单元传参
    ; 入口参数:DATA1和DATA2分别存放要相加的32位数
    ; 出口参数:DATA3缓冲区存放结果
    ;说明: (1)32位数据的存放次序采用 “高高低低”的原则
    ; (2)可能产生的进位存放在 DATA3 开始的第5个字节中。
    ;-----------------------------------------------------
    pushf
    push ax
    push si
    push dx
    push bx
    ;
    xor si,si
    xor dx,dx
    ;算出低16位,进位值存在bx
    mov ax,word ptr DATA1[si]
    add ax,word ptr DATA2[si]
    mov DATA3[si],ax
    ;算出高16位,进位值存在dx
    mov ax,word ptr DATA1[si+2]
    adc ax,0 ;将上一次计算的进位值加进来
    add ax,word ptr DATA2[si+2]
    adc dx,0
    mov word ptr DATA3[si+2],ax
    mov word ptr DATA3[si+4],dx
    ;
    pop bx
    pop dx
    pop si
    pop ax
    popf
    ret
    MADD endp
    code ends
    end start
  2. T4-6-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
    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
    ;程序名:T4-6-1.asm
    ;功能:写一个实现32位数相加的子程序
    ;子程序名:MADD
    ;算法:使用循环结构

    assume cs:code,ds:data

    data segment
    DATA1 dd 0ABCDABCDh
    DATA2 dd 0CDEFCDEFh
    DATA3 dw 0,0,0,0
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    call MADD
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    MADD PROC
    ; 功能:32位数相加
    ; 传参方式:存储单元传参
    ; 入口参数:DATA1和DATA2分别存放要相加的32位数
    ; 出口参数:DATA3缓冲区存放结果
    ;说明: (1)32位数据的存放次序采用 “高高低低”的原则
    ; (2)可能产生的进位存放在 DATA3 开始的第5个字节中。
    ;-----------------------------------------------------
    pushf
    push ax
    push si
    push dx
    push bx
    ;初始化
    mov cx,2
    xor si,si
    MADD1:
    ;算出低16位,进位值存在bx
    mov ax,word ptr DATA1[si]
    adc ax,word ptr DATA2[si]
    mov DATA3[si],ax
    inc si
    inc si
    loop MADD1
    ;处理进位
    mov al,0
    adc al,0
    mov byte ptr DATA3+4,AL
    ;
    pop bx
    pop dx
    pop si
    pop ax
    popf
    ret
    MADD endp
    code ends
    end start

顺序结构和循环结构的代码行数都差不多,那哪个代码写的更加好一些。
我认为是顺序结构写的代码更加好一下,因为循环结构执行的代码比顺序结构执行的代码要多一些,其次顺序结构更容易理解。

例2

设计一个以ASCII码表示的十进制数字符串转换为二进制数的子程序。

程序流程图

T4-8

代码

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
;程序名:T4-7.asm
;功能:设计一个以ASCII码表示的十进制数字符串转换为二进制数的子程序。
;设表示十进制数不大于65535
;这个子程序有两点不足:(1)没有检查数字串中是否有非十进制数字符存在 (2)不适用于数字串表示的十进制数超过值65535的情况。
assume cs:code,ds:data

data segment
number db 5,'4','3','9','8','1' ;数组的第一个值为要处理元素的个数
data ends

code segment
start:
mov ax,data
mov ds,ax
lea bx,number
call DTOBIN
mov ax,4c00h
int 21h

;-----------------------------------------------------
DTOBIN PROC
; 功能:把用 ASCII 表示的十进制数字串转换为二进制。
; 传参方式:寄存器传参,利用约定存储单元传参
; 入口参数:DS:BX=缓冲区首地址
; 出口参数:AX=转换得到的二进制数
; 说明: (1) 数字串的首地址存储的是要处理数字的个数
;-----------------------------------------------------
push ax ;设置初始值
push cx
push dx
xor ax,ax
mov cl,[bx] ;将数字串的首地址,作为循环次数传入cl
inc bx
xor ch,ch ;初始化ch
jcxz DTOBIN2 ;jcxz如果cx为0就不做操作,主要是判断值是否为空
DTOBIN1:
mov dx,10
mul dx ;ax*dx=ax*10
mov dl,[bx] ;
inc bx
and dl,0fh ;取各位数的值
;这条指令可以去掉,因为乘数是16位,结果保存在 dx:ax 中
;而每次计算结果小于 FFFF 没有溢出的情况,所以每次 mul 执行后 DX 必然是 0 ,没必要再去初始化。
;xor dh,dh
add ax,dx ;ax=ax*10+dl
loop DTOBIN1
DTOBIN2:
pop dx
pop cx
pop bx
ret
DTOBIN endp
code ends
end start

利用堆栈传递参数

如果使用堆栈传递参数,那么主程序在调用子程序之前,把需要传递的参数依次压入堆栈,子程序从堆栈中取入口参数;如果使用堆栈传递出口参数,那么子程序在返回前,把需要返回的参数存入堆栈,主程序在堆栈中取出口参数。
优点:不占用寄存器,也无需使用额外的存储单元。
缺点:需要考虑保护寄存器

通常利用堆栈传递入口参数,利用寄存器传递出口参数

例1

写一个测量字符串长度的子程序,设字符串以 0 为结束标志。

程序流程图

T4-9

代码

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
;程序名:T4-8.asm
;功能:写一个测量字符串长度的子程序,设字符串以 0 为结束标志。
;子程序名:STRLEN
assume cs:code,ds:data

data segment
string db 'Hello',0
data ends

code segment
start:
mov ax,data
mov ds,ax

lea dx,string
push ds
push dx
call STRLEN
mov ax,4c00h
int 21h
;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
push bp
mov bp,sp
push es
push di
;
xor ax,ax
mov di,[bp+4]
mov es,[bp+6]
STRLEN1:
mov bl,es:[di]
cmp bl,0
jz STRLEN2
inc di
inc ax
jmp STRLEN1
STRLEN2:
pop di
pop es
mov sp,bp
pop bp
ret
STRLEN endp
code ends
end start

利用 call 后续区传递参数

CALL 后续区是指位于CALL指令后的存储区域。主程序在调用子程序之前,把入口参数存入CALL指令后的存储单元中,子程序根据保存在堆栈中的返回地址找到入口参数。

例1

写一个把字符串中所有大写字母转换为小写字母子程序

程序结构图

T4-10

代码

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
;程序名:T4-9.asm
;功能:把字符串中所有大写字母转换为小写字母
;子程序名:STRLWR
assume cs:code,ds:data

data segment
string db 'HELLO','$'
data ends


code segment
start:
mov ax,data
mov ds,ax
call STRLWR
dw offset string ;偏移地址和段地址参数都存放在代码区 call 指令后
dw seg data
mov dx,ax
mov ah,9
int 21h
mov ax,4c00h
int 21h
;-----------------------------------------------------
STRLWR PROC
; 功能:把字符串中的所有大写字母改为小写字母
; 传参方式:call 后续区传递参数
; 入口参数:字符串起始地址的段值和偏移在call后续区
; 出口参数:无
;-----------------------------------------------------
push bp
mov bp,sp
push si
push ds
mov si,[bp+2] ;call 指令后的偏移地址
mov ds,cs:[si+2] ;取参数:段值
mov si,cs:[si] ;取参数:偏移
STRLWR1:
mov al,[si]
cmp al,'$'
jz STRLWR3
cmp al,'A'
JB STRLWR2
cmp AL,'Z'
JA STRLWR2
add AL,'a'-'A'
mov [si],al
STRLWR2:
inc si
jmp STRLWR1
STRLWR3:
add word ptr [bp+2],4 ;修改返回地址
mov si,[bp+2]
mov ax,si ;返回值
pop ds
pop si
mov sp,bp
pop bp
ret
STRLWR endp
code ends
end start

一般不会用这种方法调用参数,容易出错。

DOS 功能调用及应用

MS-DOS 内包含了涉及设备驱动和文件管理等方面的子程序,DOS的各种命令就是通过适当地调用这些子程序实现的。DOS功能调用主要包括三方面的子恒徐:设备驱动(基本I/O)、文件管理和其他(包括内存管理、置取时间、置取中断向量、终止程序等)。

调用方法

  1. 根据情况准备 DOS 功能调用所需的参数。有部分功能是不需要参数的,但大部分调用需要入口参数,在调用前应按照要求准备好入口参数
  2. 把功能调用号送入 AH 寄存器
  3. 发送软中断指令 “int 21h”

例1

调用 “int21” 2号功能,使喇叭发出 “嘟” 的声音。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;程序名:T4-10.asm
;功能:调用 "int21" 2号功能,使喇叭发出 "嘟" 的声音。
assume cs:code


code segment
start:
mov dl,7
mov ah,2
int 21h
mov ax,4c00h
int 21h
code ends
end start

大部分功能调用都有出口参数,在调用后,可根据有关功能调用的说明取得出口参数,如2号功能。

还有个别功能很特殊,调用后就不再返回。例如 4CH 号功能就是结束程序的运行返回DOS。4CH 号功能调用有一个存放在 AL 寄存器中的入口参数,该入口参数是程序的结束码,其值大小不影响程序的结束。

例2

修改 4CH 号功能入口参数 AL 的值,查看是否会影响程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;程序名:T4-11.asm
;功能:修改 4CH 号功能入口参数 AL 的值,查看是否会影响程序。
assume cs:code


code segment
start:
mov dl,7
mov ah,2
int 21h
mov ax,4c20h
int 21h
code ends
end start

基本的 I/O 功能调用

AH 功能 调用参数 返回参数
00 程序终止(同INT 20H) CS=程序段前缀
01 键盘输入并回显 AL=输入字符
02 显示输出 DL=输出字符
03 异步通迅输入 AL=输入数据
04 异步通迅输出 DL=输出数据
05 打印机输出 DL=输出字符
06 直接控制台I/O DL=FF(输入)
DL=字符(输出)
AL=输入字符
07 键盘输入(无回显) AL=输入字符
08 键盘输入(无回显)
检测Ctrl-Break
AL=输入字符
09 显示字符串 DS:DX=串地址
‘$’结束字符串
0A 键盘输入到缓冲区 DS:DX=缓冲区首地址
(DS:DX)=缓冲区最大字符数
(DS:DX+1)=实际输入的字符数

“int 21h”中断还有很多功能,我们在需要用到某个功能的时候再去查,不需要去记

应用举例

例1

编写程序,从键盘接收输入字符,如果数字是 N,则响铃N次;如果不是数字,则不响铃;如果是 CTRL+C 结束程序。

程序流程图

Ring

代码

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
;程序名:Ring.asm
;功能:编写程序,从键盘接收输入字符,如果数字是 N,则响铃N次;如果不是数字,则不响铃;如果是 CTRL+C 结束程序。
assume cs:code,ds:data

data segment
CTRL_C equ 3
Return db 0dh,0ah,'$'
DATA1 db 'please input number 1-9,quit with ctrl-c',0dh,0ah,'$'
ERROR db 'wrong number,please input again or with ctrl-c',0dh,0ah,'$'
data ends


code segment
start:
mov ax,data
mov ds,ax
Loop0:
lea dx,DATA1
mov ah,9
int 21h
Ring0:
mov ah,1
int 21h
lea dx,Return
mov ah,9
int 21h
;如果上 ctrl+c 中之程序
cmp al,CTRL_C
jz stop
;在1-9之间就响铃
cmp al,31h
jb ERROR1
cmp al,39h
ja ERROR1
;
and al,0fh
mov cl,al
Loop1:
mov dl,7
mov ah,2
int 21h
nop
nop
nop
loop Loop1
jmp Loop0
ERROR1:
lea dx,ERROR
mov ah,9
int 21h
jmp Ring0
stop:
mov ax,4c00h
int 21h
code ends
end start

例2

写一个程序,用二进制数形式显示按键的ASCII码。

程序流程图

T4-11

代码

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
;程序名:T4-12.asm
;写一个程序,用二进制数形式显示按键的ASCII码。
assume cs:code

code segment
start:
mov ah,1
int 21h
call NEWLINE
mov bl,al
mov cx,8
next:
shl bl,1
mov dl,30h
adc dl,0
mov ah,2
int 21h
loop next
;
mov dl,'B'
mov ah,2
int 21h
;
mov ax,4c00h
int 21h

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
push ax
push dx
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
pop dx
pop ax
ret
NEWLINE endp
code ends
end start

例3

写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度.

程序流程图

  1. 十六进制转十进制(除法运算)
    T4-12

  2. 十六进制转十进制(加法运算)
    T4-12-1

代码

  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
    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
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    ;程序名:T4-13.asm
    ;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
    ;算法:HEXTODEC 使用除法运算
    assume cs:code,ds:data,ss:stack

    data segment
    buffer db 128 dup(0) ;未初始化数据要放在最前面
    string1 db 'The number of letters is: %u',0,0
    string2 db 'The number of numbers is: %u',0,0
    string3 db 'String length is: %u',0,0
    data ends

    stack segment
    print_buffer db 128 dup(0)
    stack ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    mov ax,stack
    mov ss,ax
    ;
    lea bx,buffer
    call INPUT
    ;
    push ds
    push bx
    call CHARSTAT
    add sp,4
    ;
    lea dx,string1
    push dx
    xor dx,dx
    mov dl,ah
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,string2
    push dx
    xor dx,dx
    mov dl,al
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,buffer
    push ds
    push dx
    call STRLEN
    add sp,4
    ;
    lea dx,string3
    push dx
    push ax
    call far ptr print
    ;
    mov ax,4c00h
    int 21h

    ;--------------------------------------------------------
    INPUT PROC
    ; 功能:接收输入,并存储到 128字节大小的 bufer 缓冲区中
    ; 入口参数:bx=buffer缓冲区首地址
    ; 出口参数:bx=buffer缓冲区首地址
    ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
    ;---------------------------------------------------------
    pushf
    push ax
    push bx
    INPUT1:
    mov ah,1
    int 21h
    cmp al,0dh
    jz INPUT2
    mov [bx],al
    inc bx
    jmp INPUT1
    INPUT2:
    pop bx
    pop ax
    popf
    ;
    ret
    INPUT endp

    ;-----------------------------------------------------
    CHARSTAT PROC
    ; 功能:统计字符串中的字母、数字、其他字符的个数
    ; 传参方式:堆栈传参
    ; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
    ; 出口参数:AH=字母个数、AL=数字的个数
    ;------------------------------------------------------
    push bp
    mov bp,sp
    pushf
    push es
    push di
    push bx
    ;
    mov di,[bp+4]
    mov es,[bp+6]
    xor ax,ax
    CHARSTAT1:
    mov bl,es:[di]
    ;判断是否读取结束
    cmp bl,0
    jz CHARSTAT_POP
    ;判断小写字母
    cmp bl,'a'
    jae CHARSTAT3
    ;判断大写字母
    cmp bl,'A'
    jae CHARSTAT2
    ;判断数字
    cmp bl,'0'
    jae CHARSTAT0
    inc di
    jmp CHARSTAT1
    CHARSTAT0:
    cmp bl,'9'
    jbe number
    inc di
    jmp CHARSTAT1
    CHARSTAT2:
    ;判断字母
    cmp bl,'Z'
    jb letter
    inc di
    jmp CHARSTAT1
    CHARSTAT3:
    cmp bl,'z'
    jb letter
    inc di
    jmp CHARSTAT1
    letter:
    inc ah
    inc di
    jmp CHARSTAT1
    number:
    inc al
    inc di
    jmp CHARSTAT1
    ;
    CHARSTAT_POP:
    pop bx
    pop di
    pop es
    popf
    mov sp,bp
    pop bp
    ;
    ret
    CHARSTAT endp

    ;-----------------------------------------------------
    STRLEN PROC
    ; 功能:测量字符串的长度
    ; 传参方式:堆栈传参
    ; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
    ; 出口参数:AX=字符串长度
    ;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
    STRLEN1:
    mov bl,es:[di]
    cmp bl,0
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
    STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
    STRLEN endp

    ;--------------------------------------------------------
    NEWLINE PROC
    ; 功能:形成回车和换行(光标移到写一行首)
    ; 入口参数:无
    ; 出口参数:无
    ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
    ;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
    NEWLINE endp

    print proc far
    push bp
    mov bp,sp
    push ds
    push es
    push ss
    push si
    push di
    push ax
    push bx
    push cx
    push dx

    ;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
    ;初始化寄存器
    xor si,si
    xor di,di
    mov si,[bp+8]
    lea di,print_buffer
    print1:
    mov ax,[si]
    cmp ax,0
    jz print_b
    ;
    cmp ax,'u%'
    jz decs
    mov ss:[di],al
    inc si
    inc di
    jmp print1
    ;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
    decs:
    call HEXTODEC
    ;无:输出结果
    print_b:

    mov al,'$'
    mov ss:[di],al
    mov ax,ss
    mov ds,ax
    lea dx,print_buffer
    mov ah,9
    int 21h
    ;
    pop dx
    pop cx
    pop bx
    pop ax
    pop di
    pop si
    pop ss
    pop es
    pop ds
    mov sp,bp
    pop bp
    ret
    print endp

    CalOffset proc
    ;如果 ax <9;di+2
    cmp ax,9
    jb CalOffset1
    ;如果 ax <99;di+3
    cmp ax,99
    jb CalOffset2
    ;如果 ax <999;di+4
    cmp ax,999
    jb CalOffset3
    ;如果 ax <9999;di+5
    cmp ax,9999
    jb CalOffset4
    ;如果 ax > 9999;di+5
    jmp CalOffset5
    CalOffset1:
    ;di 为了符合循环,di 多加了1
    add di,1
    jmp CalOffset6
    CalOffset2:
    add di,2
    jmp CalOffset6
    CalOffset3:
    add di,3
    jmp CalOffset6
    CalOffset4:
    add di,4
    CalOffset5:
    add di,5
    CalOffset6:
    ;将当前偏移备份到bx中
    mov bx,di
    ret
    CalOffset endp

    HEXTODEC PROC
    mov cx,10
    ;mov si,[bp+4]
    mov ax,[bp+6]
    call CalOffset
    L1:
    xor dx,dx
    div cx
    add dl,30h
    dec di
    mov ss:print_buffer[di],dl ;用di判断循环,但是存储结果di需要减1
    cmp ax,0
    jnz L1
    mov di,bx ;恢复偏移地址
    ;
    ret
    HEXTODEC endp
    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
    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
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    ;程序名:T4-13-1.asm
    ;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
    ;算法:HEXTODEC 使用加法运算
    assume cs:code,ds:data,ss:stack

    data segment
    buffer db 128 dup(0) ;未初始化数据要放在最前面
    string1 db 'The number of letters is: %u',0,0
    string2 db 'The number of numbers is: %u',0,0
    string3 db 'String length is: %u',0,0
    data ends

    stack segment
    print_buffer db 128 dup(0)
    stack ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    mov ax,stack
    mov ss,ax
    ;
    lea bx,buffer
    call INPUT
    ;
    push ds
    push bx
    call CHARSTAT
    add sp,4
    ;
    lea dx,string1
    push dx
    xor dx,dx
    mov dl,ah
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,string2
    push dx
    xor dx,dx
    mov dl,al
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,buffer
    push ds
    push dx
    call STRLEN
    add sp,8
    ;
    lea dx,string3
    push dx
    push ax
    call far ptr print
    ;
    mov ax,4c00h
    int 21h

    ;--------------------------------------------------------
    INPUT PROC
    ; 功能:接收输入,并存储到 128字节大小的 bufer 缓冲区中
    ; 入口参数:bx=buffer缓冲区首地址
    ; 出口参数:bx=buffer缓冲区首地址
    ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
    ;---------------------------------------------------------
    pushf
    push ax
    push bx
    INPUT1:
    mov ah,1
    int 21h
    cmp al,0dh
    jz INPUT2
    mov [bx],al
    inc bx
    jmp INPUT1
    INPUT2:
    pop bx
    pop ax
    popf
    ;
    ret
    INPUT endp

    ;-----------------------------------------------------
    CHARSTAT PROC
    ; 功能:统计字符串中的字母、数字、其他字符的个数
    ; 传参方式:堆栈传参
    ; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
    ; 出口参数:AH=字母个数、AL=数字的个数
    ;------------------------------------------------------
    push bp
    mov bp,sp
    pushf
    push es
    push di
    push bx
    ;
    mov di,[bp+4]
    mov es,[bp+6]
    xor ax,ax
    CHARSTAT1:
    mov bl,es:[di]
    ;判断是否读取结束
    cmp bl,0
    jz CHARSTAT_POP
    ;判断小写字母
    cmp bl,'a'
    jae CHARSTAT3
    ;判断大写字母
    cmp bl,'A'
    jae CHARSTAT2
    ;判断数字
    cmp bl,'0'
    jae CHARSTAT0
    inc di
    jmp CHARSTAT1
    CHARSTAT0:
    cmp bl,'9'
    jbe number
    inc di
    jmp CHARSTAT1
    CHARSTAT2:
    ;判断字母
    cmp bl,'Z'
    jb letter
    inc di
    jmp CHARSTAT1
    CHARSTAT3:
    cmp bl,'z'
    jb letter
    inc di
    jmp CHARSTAT1
    letter:
    inc ah
    inc di
    jmp CHARSTAT1
    number:
    inc al
    inc di
    jmp CHARSTAT1
    ;
    CHARSTAT_POP:
    pop bx
    pop di
    pop es
    popf
    mov sp,bp
    pop bp
    ;
    ret
    CHARSTAT endp

    ;-----------------------------------------------------
    STRLEN PROC
    ; 功能:测量字符串的长度
    ; 传参方式:堆栈传参
    ; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
    ; 出口参数:AX=字符串长度
    ;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
    STRLEN1:
    mov bl,es:[di]
    cmp bl,0
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
    STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
    STRLEN endp

    ;--------------------------------------------------------
    NEWLINE PROC
    ; 功能:形成回车和换行(光标移到写一行首)
    ; 入口参数:无
    ; 出口参数:无
    ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
    ;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
    NEWLINE endp

    print proc far
    push bp
    mov bp,sp
    push ds
    push es
    push ss
    push si
    push di
    push ax
    push bx
    push cx
    push dx

    ;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
    ;初始化寄存器
    xor si,si
    xor di,di
    mov si,[bp+8]
    lea di,print_buffer
    print1:
    mov ax,[si]
    cmp ax,0
    jz print_b
    ;
    cmp ax,'u%'
    jz unsigned_int
    ;
    ;cmp ax,'%s'
    ;jz %s
    ;
    ;cmp ax,'%x'
    ;jz %x
    mov ss:[di],al
    inc si
    ;inc si
    ;inc di
    inc di
    jmp print1
    ;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
    unsigned_int:
    call HEXTODEC
    ;无:输出结果
    print_b:

    mov al,'$'
    mov ss:[di],al
    mov ax,ss
    mov ds,ax
    lea dx,print_buffer
    mov ah,9
    int 21h
    ;
    pop dx
    pop cx
    pop bx
    pop ax
    pop di
    pop si
    pop ss
    pop es
    pop ds
    mov sp,bp
    pop bp
    ret
    print endp

    HEXTODEC PROC
    xor dx,dx
    mov ax,[bp+6]
    mov bx,ax
    ;结果小于9,就直接+30h,存储到ss:di中
    cmp ax,9
    jbe HEXTODEC4
    HEXTODEC1:
    add ax,6
    adc dl,dl
    sub bx,0ah
    cmp bx,9
    jbe HEXTODEC2
    jmp HEXTODEC1
    ;运算结束后,判断是否有进位
    HEXTODEC2:
    cmp dl,0
    jz HEXTODEC3
    ;有进位,先存储dl,再存储ax
    add dl,30h
    mov ss:[di],dl
    inc di
    HEXTODEC3:
    ;无进位,处理ax,并存储到 ss:di 中
    call HTASCS
    jmp HEXTODEC5
    HEXTODEC4:
    add al,30h
    mov ss:[di],al
    inc di
    HEXTODEC5:
    ;
    ret
    HEXTODEC endp


    ;-----------------------------------------------------
    HTASCS PROC
    ; 功能:把一个字大小的16进制数(每个位必须<=9),转换成4个ASCII码,存储内存中。
    ; 传参方式:寄存器传参
    ; 入口参数:AX
    ; 出口参数:ss:di(位置可自定义)
    ;-----------------------------------------------------
    mov cx,4
    mov bh,0 ;bh 作为计数器,判断最高位是否为0。默认最高位是0
    HTASCS1:
    ROL AX,1
    ROL AX,1
    ROL AX,1
    ROL AX,1
    mov bl,al
    and bl,0fh
    cmp bl,0
    jnz HTASCS2 ;判断最高位是否为0,不为0,bh置1
    cmp bh,1
    jz HTASCS3 ;如果最高位大于等于1,保存结果
    jmp HTASCS4 ;如果最高位是0,就不保存结果
    HTASCS2:
    mov bh,1
    HTASCS3:
    add bl,30h
    mov ss:[di],bl
    inc di
    HTASCS4:
    loop HTASCS1
    ret
    HTASCS endp
    code ends
    end start

T4-12-1 的子程序 “HTASCS”,将一个字的十进制数转换为ASCII,并保存到 ss:di。同时会判断最高的位置,并存储相应值。

  1. 使用 int21 接收字符串输入
    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
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    ;程序名:T4-12-2.asm
    ;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
    ;使用 int21 中断,10号功能接收输入
    assume cs:code,ds:data,ss:stack

    data segment
    MLENGTH = 128
    buffer db MLENGTH
    db 0
    db MLENGTH dup (0)
    string1 db 'The number of letters is: %u',0,0
    string2 db 'The number of numbers is: %u',0,0
    string3 db 'String length is: %u',0,0
    data ends

    stack segment
    print_buffer db 128 dup(0)
    stack ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    mov ax,stack
    mov ss,ax
    ;
    ;lea bx,buffer
    ;call INPUT
    mov dx,offset buffer
    mov ah,10
    int 21h
    ;
    push ds
    mov bx,offset buffer+2
    push bx
    call CHARSTAT
    add sp,4
    ;
    lea dx,string1
    push dx
    xor dx,dx
    mov dl,ah
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    lea dx,string2
    push dx
    xor dx,dx
    mov dl,al
    push dx
    call far ptr print
    ;
    call NEWLINE
    ;
    mov dx,offset buffer+2 ;使用 int21 中断,10号功能接收字符串,字符串首地址偏移需要+2
    push ds
    push dx
    call STRLEN
    add sp,8
    ;
    lea dx,string3
    push dx
    push ax
    call far ptr print
    ;
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    CHARSTAT PROC
    ; 功能:统计字符串中的字母、数字、其他字符的个数
    ; 传参方式:堆栈传参
    ; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
    ; 出口参数:AH=字母个数、AL=数字的个数
    ;------------------------------------------------------
    push bp
    mov bp,sp
    pushf
    push es
    push di
    push bx
    ;
    mov di,[bp+4]
    mov es,[bp+6]
    xor ax,ax
    CHARSTAT1:
    mov bl,es:[di]
    ;判断是否读取结束,这里用0dh
    cmp bl,0dh
    jz CHARSTAT_POP
    ;判断小写字母
    cmp bl,'a'
    jae CHARSTAT3
    ;判断大写字母
    cmp bl,'A'
    jae CHARSTAT2
    ;判断数字
    cmp bl,'0'
    jae CHARSTAT0
    inc di
    jmp CHARSTAT1
    CHARSTAT0:
    cmp bl,'9'
    jbe number
    inc di
    jmp CHARSTAT1
    CHARSTAT2:
    ;判断字母
    cmp bl,'Z'
    jb letter
    inc di
    jmp CHARSTAT1
    CHARSTAT3:
    cmp bl,'z'
    jb letter
    inc di
    jmp CHARSTAT1
    letter:
    inc ah
    inc di
    jmp CHARSTAT1
    number:
    inc al
    inc di
    jmp CHARSTAT1
    ;
    CHARSTAT_POP:
    pop bx
    pop di
    pop es
    popf
    mov sp,bp
    pop bp
    ;
    ret
    CHARSTAT endp

    ;-----------------------------------------------------
    STRLEN PROC
    ; 功能:测量字符串的长度
    ; 传参方式:堆栈传参
    ; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
    ; 出口参数:AX=字符串长度
    ;------------------------------------------------------
    push bp
    mov bp,sp
    push es
    push di
    ;
    xor ax,ax
    mov di,[bp+4]
    mov es,[bp+6]
    STRLEN1:
    mov bl,es:[di]
    cmp bl,0dh ;int 21号功能,字符串最后存储的是0dh
    jz STRLEN2
    inc di
    inc ax
    jmp STRLEN1
    STRLEN2:
    pop di
    pop es
    mov sp,bp
    pop bp
    ret
    STRLEN endp

    ;--------------------------------------------------------
    NEWLINE PROC
    ; 功能:形成回车和换行(光标移到写一行首)
    ; 入口参数:无
    ; 出口参数:无
    ; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
    ;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
    NEWLINE endp

    print proc far
    push bp
    mov bp,sp
    push ds
    push es
    push ss
    push si
    push di
    push ax
    push bx
    push cx
    push dx

    ;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
    ;初始化寄存器
    xor si,si
    xor di,di
    mov si,[bp+8]
    lea di,print_buffer
    print1:
    mov ax,[si]
    cmp ax,0
    jz print_b
    ;
    cmp ax,'u%'
    jz unsigned_int
    ;
    ;cmp ax,'%s'
    ;jz %s
    ;
    ;cmp ax,'%x'
    ;jz %x
    mov ss:[di],al
    inc si
    ;inc si
    ;inc di
    inc di
    jmp print1
    ;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
    unsigned_int:
    call HEXTODEC
    ;无:输出结果
    print_b:

    mov al,'$'
    mov ss:[di],al
    mov ax,ss
    mov ds,ax
    lea dx,print_buffer
    mov ah,9
    int 21h
    ;
    pop dx
    pop cx
    pop bx
    pop ax
    pop di
    pop si
    pop ss
    pop es
    pop ds
    mov sp,bp
    pop bp
    ret
    print endp

    HEXTODEC PROC
    xor dx,dx
    mov ax,[bp+6]
    mov bx,ax
    ;结果小于9,就直接+30h,存储到ss:di中
    cmp ax,9
    jbe HEXTODEC4
    HEXTODEC1:
    add ax,6
    adc dl,dl
    sub bx,0ah
    cmp bx,9
    jbe HEXTODEC2
    jmp HEXTODEC1
    ;运算结束后,判断是否有进位
    HEXTODEC2:
    cmp dl,0
    jz HEXTODEC3
    ;有进位,先存储dl,再存储ax
    add dl,30h
    mov ss:[di],dl
    inc di
    HEXTODEC3:
    ;无进位,处理ax,并存储到 ss:di 中
    call HTASCS
    jmp HEXTODEC5
    HEXTODEC4:
    add al,30h
    mov ss:[di],al
    inc di
    HEXTODEC5:
    ;
    ret
    HEXTODEC endp


    ;-----------------------------------------------------
    HTASCS PROC
    ; 功能:把一个字大小的16进制数(每个位必须<=9),转换成4个ASCII码,存储内存中。
    ; 传参方式:寄存器传参
    ; 入口参数:AX,
    ; 出口参数:ss:di(位置可自定义)
    ;-----------------------------------------------------
    mov cx,4
    mov bh,0 ;bh 作为计数器,判断最高位是否为0。默认最高位是0
    HTASCS1:
    ROL AX,1
    ROL AX,1
    ROL AX,1
    ROL AX,1
    mov bl,al
    and bl,0fh
    cmp bl,0
    jnz HTASCS2 ;判断最高位是否为0,不为0,bh置1
    cmp bh,1
    jz HTASCS3 ;如果最高位大于等于1,保存结果
    jmp HTASCS4 ;如果最高位是0,就不保存结果
    HTASCS2:
    mov bh,1
    HTASCS3:
    add bl,30h
    mov ss:[di],bl
    inc di
    HTASCS4:
    loop HTASCS1
    ret
    HTASCS endp
    code ends
    end start

上面的程序执行结束后会出现问题,第一行的输出结果会错误,但是在调试过程中是正确的。

图片

我认为是 int21 的10号功能,接收字符串输入之后,屏幕光标位置会重新定位还原到之前的输入点。后面输出结果会覆盖之前的输入的字符串,问题在于当前输出的结果,没有输入字符串长,那么多出来的这几个字符并不会被覆盖,导致第一行显示结果错误。

例4

写一个显示指定内存单元内容的程序。具体要求是:用户按十六进制数的形式输入指定内存单元的段值和偏移,然后用十六进制数形式显示指定字节单元的内容。

程序流程图

T4-13

代码

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
;程序名:T4-13.asm
;写一个显示指定内存单元内容的程序。
;具体要求是:用户按十六进制数的形式输入指定内存单元的段值和偏移,然后用十六进制数形式显示指定字节单元的内容。
;子程序名:GETADR
assume cs:code,ds:data

data segment
addr dw 0
string db '1'
segr db 0,0,0,0
error db 'Please enter the correct memory address','$'
data ends

code segment
start:
mov ax,data ;这里的data是随机变动的,要输出指定位置的内存是需要指定段地址
mov ds,ax
;
lea si,segr
lea di,addr
call GETADR
;
mov dl,20h
mov ah,2
int 21h
;
mov al,byte ptr es:[di]
call AHTOASC
mov cx,2
mov dx,ax
L1:
xchg dh,dl
mov ah,2
int 21h
loop L1
;
mov ax,4c00h
int 21h

;-----------------------------------------------------
GETADR PROC
; 功能:用户输入的十六进制转换成二进制 "段值:偏移"
; 传参方式:ds:si=需要处理数据的缓冲区首地址,ds:di=存入结果的缓冲区首地址
; 入口参数:无
; 出口参数:ES:DI
;------------------------------------------------------
pushf
push ax
push dx
push bx
;
;获取段地址
call GETSTR
mov bx,[di]
mov es,bx
;获取分隔符
mov ah,1
int 21h
;获取偏移
call GETSTR
mov bx,[di]
mov di,bx
;判断分隔符是否为冒号
cmp al,3ah
jnz GETADR_error
;
mov ax,es
cmp ax,-1
jz GETADR_error
cmp di,-1
jz GETADR_error
;
GETADR1:
pop bx
pop dx
pop ax
popf
ret
GETADR_error:
call NEWLINE
mov dx,offset error
mov ah,9
int 21h
mov ax,4c00h
int 21h
; pop bx
; pop dx
; pop ax
; popf
GETADR endp

;--------------------------------------------------------
GETSTR PROC
; 功能:接收4个键盘输入0~9、A~F、a~f。
; 入口参数:si=buffer缓冲区首地址
; 出口参数:di=存储结果的缓冲区首地址,如果输入错误bx=-1
;---------------------------------------------------------
pushf
push ax
push cx
push si
xor bx,bx
xor ax,ax
mov cx,4
GETSTR0:
;接收输入
mov ah,1
int 21h

;处理数字
sub al,30h
jb GETSTR_error
cmp al,9
ja GETSTR1
jmp GETSTR_loop
;
GETSTR1:
;处理大写字母
sub al,7
cmp al,0ah
jb GETSTR_error
cmp al,0fh
ja GETSTR2
jmp GETSTR_loop
GETSTR2:
;处理小写字母
sub al,20h
cmp al,0ah
jb GETSTR_error
cmp al,0fh
ja GETSTR_error
jmp GETSTR_loop
GETSTR_error:
mov bx,-1
jmp GETSTR_pop
;
GETSTR_loop:
;将每次循环处理好的字符存到buffer中
mov byte ptr [si],al
inc si
loop GETSTR0
;
GETSTR_pop:
sub si,4
call INOADR
;
pop si
pop cx
pop ax
popf
ret
GETSTR endp

;--------------------------------------------------------
INOADR PROC
; 功能:将处理好的输入,转换为地址
; 入口参数:si=需要处理数据的缓冲区首地址,di=存入结果的buffer缓冲区首地址
; 出口参数:di=存入结果的buffer缓冲区首地址
;---------------------------------------------------------
pushf
push ax
push cx
push si
;
xor ax,ax
xor bx,bx
mov dx,4
mov cx,4
INOADR1:
;处理数据
mov al,byte ptr [si]
inc si
or bl,al
dec dx
jz INOADR2
shl bx,cl
jmp INOADR1
INOADR2:
mov word ptr [di],bx
pop si
pop cx
pop ax
popf
ret
INOADR endp




;-----------------------------------------------------
AHTOASC PROC
;
; 功能:把8位二进制数转换为2位 ASCII 码
; 传参方式:寄存器传参
; 入口参数:AL=要转换的值
; 出口参数:AH = 十六进制数高位的 ASCII 码
; AL = 十六进制数低位的 ASCII 码
;其他说明:(1)近过程
; (2)除 AX 寄存器外,不影响其他寄存器
; (3)调用HTOASC 实现十六进制数到ASCII码的转换
;-----------------------------------------------------
mov ah,al
shr al,1
shr al,1
shr al,1
shr al,1
call HTOASC
xchg ah,al
call HTOASC
ret
AHTOASC endp

;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
pushf
and al,0fh
cmp al,0ah
jb num_add
add al,37h
jmp pop_stack
num_add:
add al,30h
pop_stack:
popf
ret
HTOASC endp

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
push ax
push dx
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
pop dx
pop ax
ret
NEWLINE endp
code ends
end start

磁盘文件管理及应用

DOS磁盘文件管理功能调用是 DOS 功能的重要组成部分,不仅有助于汇编语言程序设计练习,也有助于文件管理系统的理解。

DOS 磁盘文件管理功能调用

  1. DOS 磁盘文件管理功能调用中,用于表示文件名的ASCII字符串必须以 ASCII 码值 0 结尾,这样的字符串通常称为 ASCIIZ 串。
  2. 文件名可以是包含盘符和路径的文件标识。
  3. 如果没有盘符,那么默认是当盘,如果路径不是从根目录开始,那么就会从当前目录开始。

这些功能调用均利用标志 CF 表示调用是否从成功,如果不成功,那么AX含有错误代码。常见错误代码如下:

错误代码 描述
01 无效的功能号
02 文件未找到
03 路径未找到
04 同时打开文件太多
05 拒绝存取
06 无效的文件号(柄)

创建文件(3CH号功能调用)

功能:创建一个新的或老的文件
入口参数:
DS:DX=文件名字符串首地址
CX=文件属性
出口参数:
CF=0 表示成功,AX=文件号(柄)
CF=1 表示失败,AX=错误代码

说明:

  1. 可指定的文件属性
代码号 描述
00H 普通
01H 只读
02H 隐含
04H 系统
  1. 文件创建成功后,文件长度定为0

打开文件(3DH号功能调用)

功能:打开文件
入口参数:
DS:DX=代表文件名的字符串的首地址
AL=存取方式
出口参数:
CF=0 表示成功,AX=文件号(柄)
CF=1 表示失败,AX=错误代码。

  1. 存取方式规定如下:
代码号 描述
00H 只读方式
01H 只写方式
02H 独写方式
  1. 文件打开成功后,文件指针定位于开始的第一个字节(偏移0)处。

读文件(3FH号功能调用)

功能:读文件
入口参数:
BX=文件号(柄)
CX=字节数
DS:DX=准备存放所读数据的缓冲区首地址。
出口参数:
CF=0 表示成功,AX=实际读到的字节数。
CF=1 表示失败,AX=错误代码

说明:

  1. 通常情况下,实际读到的字节数与需要读入的字节数相同,除非不够读。
  2. 缓冲区应保证能容下所读到的数据
  3. 文件应以读或读写方式打开
  4. 读文件后,文件指针将定位到读出字节之后的第一个字节处

写文件(40H号功能调用)

功能:写文件
入口参数:
BX=文件号(柄)
CX=写入字节数
DS:DX=存放写数据的缓冲区的首地址
出口参数:
CF=0 表示成功,AX=实际写入的字节数
CF=1 表示失败,AX=错误代码
说明:

  1. 通常情况下,实际写入的字节数与需要写入的字节数相同,除非磁盘已满
  2. 文件应以写或读写的方式打开
  3. 写文件后,文件指针将定位到写入字节后的第一个字节数

关闭文件(3EH号功能)

功能:关闭文件
入口参数:
CF=0 表示成功
CF=1 表示失败

说明:

  1. 文件号是打开文件时系统给定的文件号。

移动文件读写指针(42H号功能调用)

功能:移动文件(读写)指针
入口参数:
CF=0 表示成功,此时 DX:AX=移动后的文件指针值。
CF=1 表示失败,此时 AX=1 表示无效的移动方式,AX=6 表示无效的文件号。
说明:

  1. 文件指针值(双字)是以问年间首字节为0计算的。

  2. 移动方式和表示的意义如下:

    代码号 描述
    00H 移动文件后指针值=0(文件头)+移动位移量
    01H 当前文件指针值+移动位移量
    02H 文件长(文件尾)+移动位移量
  3. 在第一种移动方式中,移动位移量总是正的

  4. 在后两种移动方式中,移动位移量可正可负

  5. 该子功能不考虑文件指针是否超出文件范围

删除文件(41H号功能调用)

功能:删除文件
入口参数:
DS:DX=代表文件名的字符串首地址。
出口参数:
CF=0 表示成功
CF=1 表示失败,AX=错误代码
说明:只能删除一个普通文件

例1

显示文本内容。文本文件固定为当前目录下的test.txt
文件读取流程:打开文件、读取文件、输出内容、关闭文件

打开文件:以只读方式打开文件,并且根据返回值输出不同的信息。
读文件:传入相应参数,判断有效读取字节数(AX)和将要读取字节数(CX)是否一致
关闭文件:如不能关闭文件,提示错误信息。

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
;程序名:T4-14.asm
;功能:显示文本内容。文本文件固定为当前目录下的test.txt
;执行过程:打开文件,按顺序读取文件,每次读一个字符,把所有字符在屏幕上显示出来,关闭文件。
;txt文本文件结束符ASCII为1AH

assume cs:code,ds:data

data segment
fname db 'test.txt',0
handle dw 0
buffer db 0
flag db 0
error1 db 'ERROR: Invalid function',07h,24h
error2 db 'ERROR: file cannot be found',07h,24h
error3 db 'ERROR: path not found',07h,24h
error4 db 'ERROR: Too many files open at the same time',07h,24h
error5 db 'ERROR: Access Denied',07h,24h
error6 db 'ERROR: cannot close file',07h,24h
data ends

code segment
start:
mov ax,data
mov ds,ax
;
lea dx,fname
call far ptr openfile
;
read:
mov bx,handle
lea dx,buffer
mov cx,1
call far ptr readfile
mov ah,flag
cmp ah,1
jz closes
;
mov dl,buffer
cmp dl,0
jz closes
cmp dl,1Ah
jz closes
call far ptr output
jmp read
;
closes:
mov bx,handle
call far ptr closefile
;
mov ax,4c00h
int 21h

;入口参数:DS:DX
;出口参数:缓冲区handle
openfile proc far
push bp
push si
push dx
;
xor si,si
mov ax,3d00h ;以只读方式打开文件
int 21h
jc next
mov handle[si],ax
jmp stack
next:
xor dl,dl
adc dl,0
mov flag,dl
;
xor bh,bh
mov bl,2
error:
cmp al,bl
jz error22
jz error33
jz error44
inc bl
jmp error
error22:
mov dx,offset error2
jmp outputop
error33:
mov dx,offset error3
jmp outputop
error44:
mov dx,offset error3
outputop:
mov ah,9
int 21h
stack:
pop si
pop dx
pop bp
ret
openfile endp

;入口参数:bx(文件句柄)、dx 数据缓冲区地址、cx:读取多少字节
readfile proc far
;读取一个字节
mov ah,3Fh
int 21h
;
cmp ax,cx
jz stack1
mov buffer,0
stack1:
ret
readfile endp

output proc far
push ax
mov ah,02
int 21h
pop ax
ret
output endp

closefile proc far
mov ah,3EH
int 21h
jnc stack2
;
mov dl,0ah
mov ah,2
int 21h
mov dx,offset error6
mov ah,9
int 21h
stack2:
ret
closefile endp
code ends
end start

这段代码中多写了一些不必要且重复的判断。这个程序当时写的急,没有把各个子程序的信息详细写明。

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
;程序名:T4-14-1.asm
;功能:写一个显示文本文件内容的程序。文本文件固定为当前目录下的 TEST.TXT 文件
;具体算法:(1) 打开文件
; (2) 按顺序读取文件,每次读一个字符,把所读字符在屏幕上显示出来
; (3) 关闭文件
;说 明:考虑到 TEST.TXT 是文本文件,所以认为 ASCII 码的值为 1AH 的字符就是文件结束符。
;子程序名:READCH
assume ds:data,cs:code

EOF = 1AH
data segment
fname db 'test.txt',0
error1 db 'File not found',07h,0
error2 db 'Reading error',07h,0
buffer db ?
data ends


code segment
start:
mov ax,data
mov ds,ax
;
mov dx,offset fname
mov ax,3d00h ;只读模式打开文件
int 21h
jnc open_ok ;打开成功
;
mov si,offset error1
call DMESS ;显示打开不成功的信息
jmp over
;
open_ok:
mov bx,ax
cont:
call READCH ;从文件中读一个字符
jc READERR
cmp al,EOF
jz type_ok
call putch
jmp cont
;
READERR:
mov si,offset error2
call DMESS
;
TYPE_OK:
mov ah,3Eh
int 21h
over:
mov ax,4c00h
int 21h
;-----------------------------------------------------
READCH PROC
; 功能:每次从文件中按顺序读一个字符。
; 传参方式:寄存器传参
; 入口参数:AL=十六进制
; 出口参数:AX=高4位,低4位
; 说 明:子程序通过进位标志CF来反映是否正确读到字符,
;如果读时发生错误,则CF置位,否则CF清零。考虑到万一文本
;文件没有文件结束符的情况,所以该子程序还判断是否的却已
;读到文件尾(如果实际读到的字符为0就意味着文件结束),
;当这种情况发生时,就返回一个文件结束符。
;------------------------------------------------------
mov cx,1
mov dx,offset buffer
mov ah,3FH
int 21h
jc READCH2
cmp ax,cx
mov al,EOF
jb READCH1
mov AL,BUFFER
READCH1:
CLC
READCH2:
ret
READCH endp

;-----------------------------------------------------
DMESS PROC
;
; 功能:显示一个以 0 结束符的字符串
; 入口参数:SI = 字符串首地址
; 出口参数:无
;-----------------------------------------------------
DMESS1:
mov DL,[SI]
inc SI
or DL,DL
jz DMESS2
mov ah,2
int 21h
jmp DMESS1
DMESS2:
ret
DMESS endp
;-----------------------------------------------------
PUTCH PROC
;
; 功能:使用 int 21h的 2 号功能输出字符。
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:无
;-----------------------------------------------------
push dx
mov dl,al
mov ah,2
int 21h
pop dx
ret
PUTCH endp
code ends
end start

例2

把键盘上输入的全部字符,存入某个文件的程序。文件固定为当前根目录下的test.txt。如果它已经存在,则更新它。

  1. 书中的举例(重新建立 test.txt 覆盖原数据,写入输入数据)

    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
    ;程序名:T4-15.asm
    ;功能:把键盘上输入的字符全部字符(直到CTRL+Z键,值1AH)存入文件 test.txt 中,
    ;如果 test.txt 文件已存在则更新它。
    assume cs:code,ds:data

    EOF = 1AH

    data segment
    FNAME db '\TEST.txt',0
    ERRMESS1 db 'Can not create file',07h,'$'
    ERRMESS2 db 'Writing error',07h,'$'
    BUFFER db ?
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    ;
    mov dx,offset FNAME ;建立文件
    mov cx,0
    mov ah,3CH
    int 21h
    jnc CREA_OK ;建立成功,跳转
    ;
    mov dx,offset ERRMESS1 ;显示不能新建文件提示信息
    call DISPMESS
    jmp over
    ;
    CREA_OK:
    mov bx,ax ;保存文件句柄
    CONT:
    call GETCHAR ;接收一个键
    push ax
    call WRITECH ;向文件写所读的字符
    pop ax
    jc WERROR ;写出错,转
    cmp AL,EOF ;是否已读到文件结束符
    jnz CONT ;不是,继续
    jmp CLOSEF ;遇文件结束符,转结束
    ;
    WERROR:
    mov dx,offset ERRMESS2 ;显示写出错误提示信息
    call DISPMESS
    ;
    CLOSEF:
    MOV ah,3EH ;关闭文件
    int 21h
    over:
    mov ax,4c00h
    int 21h

    ;-----------------------------------------------------
    WRITECH PROC
    ;
    ; 功能:把需要写入的一个字节,送入缓冲区
    ;-----------------------------------------------------
    mov BUFFER,AL
    mov dx,offset BUFFER
    mov cx,1
    mov ah,40h
    int 21h
    ret
    WRITECH endp

    ;-----------------------------------------------------
    GETCHAR PROC
    ; 功能:输入一个字符
    ;-----------------------------------------------------
    mov ah,1
    int 21h
    ret
    GETCHAR endp

    ;--------------------------------------------------------
    DISPMESS PROC
    ; 功能:显示由 DX 所指的提示信息
    ; 入口参数:DX=字符串首地址
    ; 出口参数:无
    ;---------------------------------------------------------
    mov ah,9
    int 21h
    ret
    DISPMESS endp
    code ends
    end start
  2. 读 test 文件,在写入输入缓冲区的内容,实现文件更新

    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
    ;程序名:T4-15-1.asm
    ;功能:把键盘上输入的全部字符,存入某个文件的程序
    ;文件固定为当前根目录下的test.txt。如果它已经存在,则更新它。

    ;打开文件(判断是否已存在),如已存在:读取文件并存入缓冲区、读取键盘输入存入缓冲区
    ;如不存在:创建文件,将输入存入缓冲区

    assume cs:code,ds:data
    data segment
    fname db 'test.txt',0
    handle dw 0
    flag db 0
    read_buffer db 0
    input_buffer db 30 dup(0)
    len dw 0
    message db 'Please enter the data you want to write: ',24h
    input_error db 'Please enter char$'
    close_error db 'ERROR: cannot close file',07h,24h
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    open:
    ;打开文件,并判断是否打开成功
    call far ptr openfile
    jnc create_ok
    ;创建文件
    create:
    lea dx,fname
    mov cx,0
    mov ah,3ch
    int 21h
    jmp open ;创建文件后,还需要打开文件才能写入数据
    ;文件创建成功,提示输入
    create_ok:
    xor di,di
    read_ch:
    ;读文件,并将内容复制到缓冲区中。
    mov bx,handle
    lea dx,read_buffer
    mov cx,1
    mov ax,3Fh
    int 21h
    cmp ax,0 ;如果没有正确读到字符,ax=0
    jz input_ch
    ;将读到的字符传入写字符缓冲区(只需要将文件内容读一遍,原文件字符就不会被覆盖)
    ;mov al,read_buffer
    ;mov input_buffer[di],al
    ;inc di
    jmp read_ch

    input_ch:
    call far ptr input
    ;输入成功,将字符串写到文件中
    write:
    lea dx,input_buffer
    mov bx,handle
    mov cx,len
    mov ah,40h
    int 21h
    close:
    call far ptr closefile
    mov ax,4c00h
    int 21h

    openfile proc far
    lea dx,fname
    mov ax,3d02h ;以读写方式打开文件
    int 21h
    mov handle,ax
    stack:
    ret
    openfile endp

    input proc far
    ;打印提示信息
    lea dx,message
    mov ah,9
    int 21h
    loop_input:
    ;用户输入
    mov ah,1
    int 21h
    ;判断用户输入结束按键
    cmp al,0dh
    jz returnA
    ;根据情况设置用户输入规则
    cmp al,20h
    jb input_err
    ;
    mov input_buffer[di],al
    inc di
    jmp loop_input
    input_err:
    ;换行
    mov dl,0ah
    mov ah,2
    int 21h
    ;
    lea dx,input_error
    mov ah,9
    int 21h
    jmp stop
    stop:
    mov ax,4c00h
    int 21h
    returnA:
    mov input_buffer[di],0 ;1AH
    mov len,di
    ret
    input endp

    closefile proc far
    mov bx,handle
    mov ah,3EH
    int 21h
    jnc stack2
    ;
    mov dl,0ah
    mov ah,2
    int 21h
    mov dx,offset close_error
    mov ah,9
    int 21h
    stack2:
    ret
    closefile endp
    code ends
    end start
  3. 使用42h号功能,修改文件读写指针,实现更新功能

    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
    ;程序名:T4-15.asm
    ;功能:把键盘上输入的全部字符(直到Ctrl+Z键,值1AH)存入某个文件的程序
    ;文件固定为当前根目录下的test.txt。如果它已经存在,则更新它
    ;算法:使用 42H 号功能实现

    assume cs:code,ds:data
    data segment
    fname db 'test.txt',0
    handle dw 0
    flag db 0
    read_buffer db 0
    input_buffer db 30 dup(0)
    len dw 0
    message db 'Please enter the data you want to write: ',24h
    input_error db 'Please enter char$'
    close_error db 'ERROR: cannot close file',07h,24h
    data ends

    code segment
    start:
    mov ax,data
    mov ds,ax
    open:
    ;打开文件,并判断是否打开成功
    call far ptr openfile
    jnc create_ok
    ;创建文件
    create:
    lea dx,fname
    mov cx,0
    mov ah,3ch
    int 21h
    jmp open ;创建文件后,还需要打开文件才能写入数据
    ;文件创建成功,提示输入
    create_ok:
    ;修改读写指针
    mov bx,handle
    xor cx,cx
    xor dx,dx
    mov ax,4202h
    int 21h

    input_ch:
    call far ptr input
    ;输入成功,将字符串写到文件中
    write:
    lea dx,input_buffer
    mov bx,handle
    mov cx,len
    mov ah,40h
    int 21h
    close:
    call far ptr closefile
    mov ax,4c00h
    int 21h

    openfile proc far
    lea dx,fname
    mov ax,3d02h ;以读写方式打开文件
    int 21h
    mov handle,ax
    stack:
    ret
    openfile endp

    input proc far
    ;打印提示信息
    lea dx,message
    mov ah,9
    int 21h
    loop_input:
    xor di,di
    ;用户输入
    mov ah,1
    int 21h
    ;判断用户输入结束按键
    cmp al,0dh
    jz returnA
    ;根据情况设置用户输入规则
    cmp al,20h
    jb input_err
    ;
    mov input_buffer[di],al
    inc di
    jmp loop_input
    input_err:
    ;换行
    mov dl,0ah
    mov ah,2
    int 21h
    ;
    lea dx,input_error
    mov ah,9
    int 21h
    jmp stop
    stop:
    mov ax,4c00h
    int 21h
    returnA:
    mov input_buffer[di],0 ;1AH
    mov len,di
    ret
    input endp

    closefile proc far
    mov bx,handle
    mov ah,3EH
    int 21h
    jnc stack2
    ;
    mov dl,0ah
    mov ah,2
    int 21h
    mov dx,offset close_error
    mov ah,9
    int 21h
    stack2:
    ret
    closefile endp
    code ends
    end start

例3

写一个程序把文件2拼接到文件1上。文件1固定为当前目录下的TEST1,文件2固定为当前目录下的TEST2
该示例是使用到 21中断的42h号 功能,疑问文件读写指针实现的。

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
;程序名:T4-17.asm
;功能:写一个程序把文件2拼接到文件1上。文件1固定为当前目录下的TEST1,文件2固定为当前目录下的TEST2
assume cs:code,ds:data
;符号常量定义
BUFFLEN = 512
;数据段
data segment
fname1 db 'test1.txt',0
fname2 db 'test2.txt',0
handle1 dw 0
handle2 dw 0
ERRMESS1 db 'Can not open file',07h,'$'
ERRMESS2 db 'Reading eror',07h,'$'
ERRMESS3 db 'Writing error',07h,'$'
BUFFER db BUFFLEN dup(0)
data ends
;代码段
code segment
start:
mov ax,data
mov ds,ax
mov dx,offset fname1
mov ax,3d01h
int 21h
jnc OPENOK1
;
ERR1:
mov dx,offset ERRMESS1
call DISPMESS
jmp OVER
OPENOK1:
mov handle1,ax
mov dx,offset fname2
mov ax,3d00h
int 21h
jnc OPENOK2
;
mov bx,handle1
mov ah,3Eh
int 21h
jmp ERR1
OPENOK2:
mov handle2,ax
mov bx,handle1
xor cx,cx
xor dx,dx
mov ax,4202h
int 21h
;
CONT:
mov dx,offset BUFFER
mov cx,BUFFLEN
mov bx,handle2
mov ah,3fh
int 21h
jc RERR
;
or ax,ax
jz copyok
mov cx,ax
mov bx,handle1
mov ah,40h
int 21h
jnc CONT
WERR:
mov dx,offset ERRMESS3
call DISPMESS
jmp SHORT COPYOK
;
RERR:
mov dx,offset ERRMESS2
call DISPMESS
COPYOK:
mov bx,handle1
mov ah,3Eh
int 21h
mov BX,handle2
mov AH,3Eh
int 21h
;
OVER:
mov ah,4ch
int 21h
;--------------------------------------------------------
DISPMESS PROC
; 功能:显示由 DX 所指的提示信息
; 入口参数:DX=字符串首地址
; 出口参数:无
;---------------------------------------------------------
mov ah,9
int 21h
ret
DISPMESS endp
code ends
end start
  • 本文标题:8086汇编-子程序设计
  • 本文作者:9unk
  • 创建时间:2022-08-16 21:53:00
  • 本文链接:https://9unkk.github.io/2022/08/16/8086-hui-bian-zi-cheng-xu-she-ji/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!