汇编语言期末复习

题目解答

一 基础知识

1.若CPU的寻址能力是 2^n字节(Byte),那么地址总线宽度为 n 位。

8KB = 2^13byte

所以为13

2.1KB存储器的地址范围, 1KB = 1024个存储单元,地址从0开始编号

所以为0-1023

3.1KB存储器可存储的数据量,1KB = 2^13bit

可存储 2^13 bit

可存储 2^10 Byte

4.常见的转换:::

1KB = 2^10Byte

1MB = 2^20Byte

1GB = 2^30Byte

6.传输1024字节所需次数

若数据总线宽度为 n 位,则每次传输 n/8 字节

所需次数 = 总字节数 ÷ 每次字节数

8086(16位)→ 一次2B → =512 次

80386(32位)→ 一次4B → =256 次

8080:8位 → 1B

8088:8位 → 1B

8086:16位 → 2B

80286:16位 → 2B

80386:32位 → 4B

二 寄存器

1.补充完整:

mov 是赋值指令,不影响标志位。

add 是加法指令,结果存入目标寄存器。

ax 包括 ah(高8位)和 al(低8位)。

指令演示:

指令 执行后寄存器值 说明
mov ax,62627 AX = F4A3H 62627₁ = F4A3₁₆
mov ah,31H AX = 31A3H 修改高 8 位
mov al,23H AX = 3123H 修改低 8 位
add ax,ax AX = 6246H 3123H + 3123H
mov bx,826CH BX = 826CH 直接赋值
mov cx,ax CX = 6246H AX → CX
mov ax,bx AX = 826CH BX → AX
add ax,bx AX = 04D8H 826CH + 826CH = 104D8H → 截断为 04D8H
mov al,bh AX = 0482H BH → AL(AL = 82H),AH 未变(仍为 04H)
mov ah,bl AX = 6C82H BL → AH(AH = 6CH)
add ah,ah AX = D882H AH = 6C + 6C = D8H
add al,6 AX = D888H AL = 82H + 06H = 88H
add al,al AX = D810H AL = 88H + 88H = 110H → 截断为 10H
mov ax,cx AX = 6246H CX → AX

2.用最多4条指令计算 2⁴

每次 add ax, ax 就是乘以 2,相当于左移 1 位

mov ax,2
add ax,ax ; 2 × 2 = 4
add ax,ax ; 4 × 2 = 8
add ax,ax ; 8 × 2 = 16

3.段地址 0001H 时的寻址范围

实地址 = 段地址 × 16 + 偏移地址

段地址 0001H → 00010H 实起始地址

最大偏移 = FFFFH → 最大地址 = 00010H + FFFFH = 1000FH

所以寻址的范围就是00010H-1000FH

4.要访问实地址 20000H,求段地址 SA 的范围

实地址 = SA × 10H + 偏移

设偏移 = 0 → SA 最小 = 20000H ÷ 10H = 2000H

设偏移最大 = FFFFH → SA 最大 = (20000H - FFFFH) ÷ 10H ≈ 1001H

最小段地址:1001H

最大段地址:2000H

5.

sub ax, ax 用 AX 自己减自己,结果赋给 AX 这样的结果为0

清零首选 xor ax, ax

jmp ax,跳转到 AX 中存储的地址

IP 修改次数:4 次

修改时机:

  1. mov 指令加载 → IP++
  2. sub 指令加载 → IP++
  3. jmp 指令加载 → IP++
  4. jmp 执行 → IP ← AX

最后 IP 的值: 0000H

九 跳跃

1.段内转移指令 jmp word ptr [bx+1]

1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code
data segment
?
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0
jmp word ptr [bx+1] ; 段内跳转,偏移地址来自ds:[bx+1]
code ends
end start

jmp word ptr [bx+1] 表示从内存地址 ds:[1]ds:[2] 处读取2字节构成目标偏移地址,CS不变。

要跳到 IP = 0000H,那么 ds:[1~2] = 0000H

所以只需确保 data 段的第二、三字节是 0。

db 4 dup (0) ; 定义4字节,确保ds:[1]和ds:[2]都是0

2.段间跳跃,jmp dword ptr ds:[0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data segment
dd 12345678H
data ends

code segment
start:
mov ax,data
mov ds,ax
mov bx,0
mov [bx],____
mov [bx+2],____
jmp dword ptr ds:[0]
mov ax,4c00h
int 21h
code ends

jmp dword ptr ds:[0]段间跳转,执行时:

  • ds:[0]~[1] → 被解释为 IP(偏移地址)
  • ds:[2]~[3] → 被解释为 CS(段地址)

当前指令起始处 IP = 0000H(或 offset start

当前 CS = cs,跳转目标就是 CS:0

1
2
3
mov [bx], bx    ; 相当于 mov [0], 0
mov [bx+2], cs ; 相当于 mov [2], cs

3.指令 jmp dword ptr es:[1000H],查看内存内容分析跳转结果

2000:1000 BE 00 06 00 00 00 …

1
2
3
mov ax,2000H
mov es,ax
jmp dword ptr es:[1000H]

段间跳转,目标是 es:[1000H] 开始的4字节:

  • 低地址两字节(BE 00) → IP = 00BEH
  • 高地址两字节(06 00) → CS = 0006H

跳转后:CS = 0006H, IP = 00BEH

4.使用 jcxz 查找第一个值为0的字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
code segment
start:
mov ax,2000H
mov ds,ax
mov bx,0
s: mov cl,ds:[bx] ; 将内存内容加载到CL
mov ch,0 ; 组成CX = 0000 ~ 00FF
jcxz ok ; 如果 CX = 0, 跳转
add bx,1
jmp short s
ok: mov dx,bx ; BX 即偏移位置
mov ax,4c00h
int 21h
code ends

每次加载一个字节到 CL,然后组成 16 位 CX(CH=0)

如果 CX=0,说明找到了值为0的字节

jcxz 检查,找到后跳转到 ok

最终将偏移地址保存在 DX 中

5.使用 loop 查找第一个为0的 byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
code segment
start:
mov ax,2000H
mov ds,ax
mov bx,0
s: mov cl,[bx]
mov ch,0
add cx,1 ; 如果 cx = 0,add cx,1 得到 1,loop 继续
inc bx ; 查下一个字节
loop s
ok: dec bx ; 多加了一次 bx,回退
mov dx,bx
mov ax,4c00h
int 21h
code ends

  • loop指令减少 CX 并跳转,直到 CX=0
  • cl=[bx] 加载内存,若为0,则 cx=0add cx,1 变成 1,loop执行后 cx=0,跳出
  • 最终偏移值存在 bx 中,但已多加一,因此 dec bx 修正偏移

1.内存查看与汇编执行分析(x)

0000:0000 70 80 F0 30 EF 60 30 E2 00 80 80 12 66 20 22 60
0000:0010 62 26 E6 D6 CC 2E 3C 3B AB BA 00 00 26 06 66 88

汇编指令 AX BX 说明
mov ax,1 0001 0000 AX ← 1
mov ds,ax 0001 0000 DS ← AX,不影响 AX/BX
mov ax,[0000] 2662 0000 AX ← DS:0000 = 0x8026(注意是低地址在前)
mov bx,[0001] 2662 E626 BX ← DS:0001 = 0x26E6(从地址0001开始读两个字节)
mov ax,bx E626 E626 AX ← BX
mov ax,[0000] 2662 E626 AX ← DS:0000
mov bx,[0002] 2662 D6E6 BX ← DS:0002 = 0xE6D6
add ax,bx FD48 D6E6 AX = 2662 + D6E6 = FD48
add ax,[0004] 2C14 D6E6 AX += DS:0004 = 0x602C → FD48+602C=15D74 → 截断为2C14
mov ax,0 0000 D6E6 清零 AX
mov al,[0002] 00E6 D6E6 AL ← E6(AX低字节),AH=0 → AX=00E6
mov bx,0 00E6 0000 BX 清零
mov bl,[000C] 00E6 0026 BL ← DS:000C = 26H → BX=0026
add al,bl 000C 0026 AL=E6+26=10C → 截断为 0C,AX=000C

2.程序执行追踪 + 汇编流程图(x)

mov ax,6622H
jmp 0ff0:0100H
mov ax,2000H
mov ds,ax
mov ax,[0008]
mov ax,[0002]

指令 AX BX CS IP DS
mov ax,6622H 6622 0000 2000 0003 1000
jmp 0ff0:0100H 6622 0000 0FF0 0100 1000
mov ax,2000H 2000 0000 0FF0 0103 1000
mov ds,ax 2000 0000 0FF0 0105 2000
mov ax,[0008] C189 0000 0FF0 0108 2000
mov ax,[0002] EA66 0000 0FF0 010B 2000

3.逆序拷贝程序(使用栈)

10000H~1000FH 的 8 个字(16 字节)将 10000H~1000FH 中的 8 个字逆序拷贝到 20000H~2000FH 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mov ax,1000H
mov ds,ax ; 数据段指向 10000H,就是原始地址

mov ax,2000H
mov ss,ax ; 栈段指向 20000H,就是目的地址
mov sp,0010H ; 栈顶为 20000:0010

push [0]
push [2]
push [4]
push [6]
push [8]
push [0AH]
push [0CH]
push [0EH]

栈是从高地址向低地址存储,push 后栈内容为 [0EH], [0CH], ..., [0]

注意 [0] 等都是 ds 段内偏移地址

出栈写入(pop)将 10000H~1000FH 中的 8 个字逆序拷贝到 20000H~2000FH 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mov ax,2000H
mov ds,ax
mov ax,1000H
mov ss,ax
mov sp,0000H
pop [E]
pop [C]
pop [A]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]

DS(Data Segment,数据段寄存器)
指向当前程序使用的数据段基地址,用于访问数据。

SS(Stack Segment,栈段寄存器)
指向当前栈所在的段基地址。

SP(Stack Pointer,栈指针寄存器)
指示当前栈顶(栈顶指针)的偏移地址,结合 SS 寄存器,确定栈顶的线性地址。

1
2
mov ax,1000H
mov ds,ax

说明数据段 DS 指向段地址 1000H,即内存中以 10000H 为起始的地址区域。

SP 是栈顶偏移量,相对于 SS 指定的段地址。

1.利用 retf 实现远跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:code
stack segment
db 16 dup(0)
stack ends

code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,1000h
push ax
mov ax,0
push ax
retf
code ends
end start

1
2
3
4
5
6
mov ax,1000h   ; 目标段地址
push ax ; 压栈:目标段
mov ax,0 ; 目标偏移地址
push ax ; 压栈:目标偏移
retf ; 弹出两个字:IP ← [SP],CS ← [SP+2]

从当前程序跳转到 1000:0000 开始执行。

2.call 段内过程 的本质

1
2
3
4
5
1000:0000 b8 00 00        mov ax,0
1000:0003 e8 01 00 call s
1000:0006 40 inc ax
1000:0007 58 s: pop ax

1
2
3
4
5
mov ax,0
call s ; 将下一条指令地址(1000:06)压栈
inc ax ; IP 跳转后这句不会先执行
s: pop ax ; 从栈中弹出 1000:06,ax=6

call s返回地址 1000:06 压入栈

pop ax弹出该地址,ax=6

3.机械码

1
2
3
4
5
6
7
8
1000:0000 b8 00 00        mov ax,0
1000:0003 9a 09 00 00 10 call far ptr s ; 调用 1000:0009
1000:0008 40 inc ax
1000:0009 58 s: pop ax
05 00 add ax, ax
1f pop bx
03 d8 add ax, bx

执行后,ax的值为多少?

pop ax → ax = 0008h(返回地址)

add ax, ax → ax = 0x10

pop bx → bx = 1000h(段)

add ax, bx → ax = 0x10 + 0x1000 = 0x1010

4.call ax(寄存器间接调用)

1
2
3
4
5
1000:0000 b8 06 00       mov ax,6
1000:0003 ff d0 call ax
1000:0005 40 inc ax
1000:0006 58 pop ax

call ax 相当于 call 0006h,先把返回地址 1000:05 压栈

1000:06,执行 pop ax → ax = 0005

5.间接调用 call word ptr ds:[0eh]

1
2
3
4
5
call word ptr ds:[0eh]
inc ax
inc ax
inc ax

已知 [ds:0eh] = 0011hcall跳转到0011h后执行 inc ax 三次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code
stack segment
dw 8 dup (0)
stack ends

code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ds,ax
mov ax,0
call word ptr ds:[0eh]
inc ax
inc ax
inc ax
mov ax,4c00h
int 21h
code ends
end start

  • 栈段 stack 定义了 8 个 word 空间,共 16 字节(从 0 到 15)。
  • 初始化:ss = stack, sp = 16,即栈顶从偏移 0010h 开始。
  • ds = ax = stack
  • call word ptr ds:[0eh]
    查找 ds:0eh 处的内容,即偏移地址(设为 0011h)

因为 call 是段内调用,所以:

  • 执行 call 0011h

    • call 会将 返回地址 (call 下一条指令地址) = 0011h 压入栈
    • IP ← 0011h
  • IP 跳转到 offset=0011h 的位置,接着执行:

    1
    2
    3
    inc ax
    inc ax
    inc ax

    初始 ax = 0 → 1 → 2 → 3

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
assume cs:codesg
stack segment
dw 8 dup (0)
stack ends

codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,10h
mov word ptr ss:[0],offset s ; (ss:[0]) = 1Ah
mov ss:[2],cs ; (ss:[2]) = 当前代码段 CS
call dword ptr ss:[0] ; 相当于 call far ptr cs:1Ah
; 执行前压栈:cs 和 IP=19h
; (ss:[4]) = cs, (ss:[6]) = 19h
nop
s:
mov ax,offset s ; ax = 1Ah
sub ax,ss:[0ch] ; ax = 1Ah - ss:[0Ch] = 1Ah - 19h = 1
mov bx,cs ; bx = cs
sub bx,ss:[0eh] ; bx = cs - ss:[0eh] = cs - cs = 0
mov ax,4c00h
int 21h
codesg ends
end start

  • ss:[0] = 1Ah,即跳转到当前段偏移地址 1Ah
  • ss:[2] = cs,即段地址
  • call dword ptr ss:[0]跳转到 cs:1Ah
  • call 指令将 返回地址 IP=19hCS=cs 入栈:
    • ss:[4] = cs
    • ss:[6] = 0019h

进入 s: 标签后:

1
2
3
4
mov ax, offset s      ; ax = 1Ah
sub ax, ss:[0ch] ; ss:[0ch] = 19h → ax = 1Ah - 19h = 1
mov bx, cs ; bx = cs
sub bx, ss:[0eh] ; ss:[0eh] = cs → bx = cs - cs = 0

1
2
ax = 1
bx = 0

十一

写出每条指令执行后,标志位的值:

指令 CF OF SF ZF PF
sub al, al 0 0 0 1 1
mov al,10H 0 0 1
add al,90H 0 0 1 0 1
mov al,80H 1 0 1
add al,80H 1 1 0 1 1
mov al,0FCH 1 0 1
add al,05H 1 0 0 0 0
mov al,7DH 0 0 0
add al,0BH 0 1 1 0 1

知识框架

简答题

1.对汇编语言的理解

概念:面向机器的低级语言,使用符号化指令(助记符)

优点

  • 执行效率高(直接操作寄存器/内存)
  • 控制力强(可精细操控硬件)
  • 有助于理解计算机体系结构

缺点

  • 语法繁琐、代码量大
  • 可读性差、维护难度高
  • 不可移植,依赖具体 CPU 架构

2.寄存器体系及其使用

标志位 含义 触发条件示例
ZF Zero Flag,零标志 运算结果为0时置位1
SF Sign Flag,符号标志 运算结果最高位为1时置位1
CF Carry Flag,进位/借位标志 无符号加法产生进位或减法借位时置1
OF Overflow Flag,溢出标志 有符号运算超出范围时置1
PF Parity Flag,奇偶标志(低8位偶数个1) 结果低8位中1的个数为偶数时置1
1
2
3
4
5
sub al,al    → ZF=1, PF=1, SF=0
mov al,10H → ZF=0, PF=1, SF=0
add al,90H → SF=1, ZF=0, PF=1
add al,80H → OF=1, CF=1, ZF=1

3.程序加载与段:偏移机制

段地址(Segment Address):指内存中一个64KB段的起始地址,由段寄存器(如 CS、DS、SS、ES)给出。

偏移地址(Offset Address):表示该段内的相对地址位置,通常由 IP、BX、SI、DI 等寄存器提供。

物理地址计算公式

1
2
编辑物理地址 = 段地址 × 16 + 偏移地址
= 段地址 << 4 + 偏移地址

4.条件与无条件跳转

jmp/ je/ jne/ jg… 指令格式

近跳转 vs 远跳转 vs 间接跳转

跳转目标:立即数、寄存器、内存

jmp 指令

jmp 段内跳转:只修改 IP,CS 不变。jmp 段间跳转:同时修改 CS 和 IP

call 指令:

实现子程序调用,会将返回地址入栈,然后跳转到目标地址。

  • 近调用(段内):只入栈 IP
  • 远调用(段间):先入栈 CS,再入栈 IP

调用完成后跳转到子程序开始位置执行。

ret / retf 指令

ret:弹出栈顶内容(IP)并跳回(适用于近调用);

retf:弹出两个字节恢复 IP,然后再弹出两个字节恢复 CS,实现段间返回。

5.基本结构

汇编程序的基本结构:由代码段(存放指令)、数据段(存放数据)、栈段(管理栈空间)组成,程序入口标签(如 start:)指明执行起点,end 标记程序结束

段寄存器的作用及设置:CS 指向代码段,DS 指向数据段,SS 指向栈段。设置时先用 mov ax, 段地址,再用 mov 段寄存器, ax 装载段地址。

汇编程序的执行流程:CPU 从 CS:IP 指向位置开始执行指令,执行后 IP 自动加指令长度,跳转指令可修改 CS 和 IP 以改变执行流。

内存访问方式:内存地址由段寄存器和偏移地址组合计算,数据访问用 DS,代码访问用 CS,栈操作用 SS。

汇编指令的基本作用:mov 指令实现数据在寄存器和内存间传送,段寄存器初始化通过先装载到通用寄存器再转移。

6.DOS 如何加载 EXE 可执行程序?

DOS 从内存中找到一块空闲区域,段地址为 SA,偏移为 0000。

在 SA:0 开始的 256 字节内创建 PSP(程序段前缀),用于 DOS 和程序通信。

程序本体加载到 SA+10H:0 处,即从 256 字节后开始执行。

PSP 和程序在物理地址上连续,但逻辑段地址不同。

最后设置 DS=SA,CS:IP=SA+10H:0,准备开始执行程序。

填空题

1.栈操作细节,push/ pop 对 SP、SS 的影响

2.转移指令格式,机器码长度、操作数类型

3.子程序调用,call/ ret 的入栈、出栈顺序

4.标志寄存器,ZF、SF、CF、OF 置位条件,哪些算术/逻辑指令影响哪些标志

程序分析题

1.loop 循环 & CX 计数寄存器变化,loop label 的执行流程——取指、CX–1、ZF检查、跳转,执行次数与初始 CX 的关系

2.子程序/间接跳转跟踪,

call/ jmp reg/ ret 全流程:

  1. IP 入栈
  2. CS:IP 更新
  3. 返回地址弹栈

3.复制程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
code segment
mov ax, 0020h
mov ds, ax ; DS = 源数据段 → 0020:0000
mov ax, 0021h
mov es, ax ; ES = 目标数据段 → 0021:0000

mov bx, 0
mov cx, 10 ; 拷贝 10 个字节(或字符)

s: mov al, [bx] ; 从 DS:[BX] 取数据(即 0020:BX)
mov es:[bx], al ; 写入 ES:[BX](即 0021:BX)
inc bx
loop s ; 循环 CX 次,CX ← CX - 1,直到 CX=0

mov ax, 4c00h
int 21h
code ends
end

将 DS:0000(0020:0000)开始的 10 个字节 拷贝到 ES:0000(0021:0000)开始的地址。

mov al, [bx] 表示:从 DS:BX 指向的内存地址中读取一个字节到 AL(间接寻址)

mov es:[bx], al 表示:将 AL 的值存入 ES:BX 指向的内存地址

loop s = dec cx; if cx != 0 then jmp s,循环控制语句,用于固定次数循环

mov ds, axmov es, ax 是设置数据段和额外段,访问内存前必须设置段寄存器

编程题目

  1. 基于 loop 的计数/求和程序(第5章)
  1. 段:偏移 数据搬运 + 栈管理(第6章)

    如:从数据段读字符串,压栈→子程序处理→弹栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:codesg
codesg segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
start: mov ax,0
mov ds,ax ; DS → 0,即访问 0:0~0:F
mov bx,0
mov cx,8
s: mov ax,[bx] ; 从内存 0000:BX 处读两个字节到 AX
mov cs:[bx],ax ; 把 AX 写入代码段 CS:BX 位置的数据
add bx,2
loop s
mov ax,4C00H
int 21H
codesg ends
end start

数据从0:0 ~ 0:F(16 字节,8 个字)依次读取,覆盖 CS 段中的初始数据区

原始数据如 0123H,0456H... 将被替换为内存中读取的新数据

效果:程序数据区被外部数据动态更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:codesg
codesg segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
dw 0,0,0,0,0,0,0,0,0,0 ; 共16字节空间,用作栈区
start: mov ax,cs ; 设定 SS = CS,栈在代码段中
mov ss,ax
mov sp,36 ; SP = 36 → 栈顶指向代码段偏移 24h
mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: push [bx] ; 从 DS:BX(即 0:0 开始)压栈
pop cs:[bx] ; 从栈中弹出数据写入 CS:BX(覆盖初始数据)
add bx,2
loop s
mov ax,4C00H
int 21H
codesg ends
end start

使用 stack 完成数据搬运,相当于:
ax ← ds:[bx] → push → 栈 → pop → cs:[bx]

使用 push/pop 方式达到 数据传送效果

栈顶设为 24h(即 36 十进制)→ 从 cs:0024h 处开始使用栈

栈段的起始位置由 ss = cs 指定,即栈放在当前代码段末尾的空区域

  1. 大小写转换(第7章)

    • 利用 AL/AH 与 ASCII 区别
    • 不同寻址方式:立即、直接、寄存器间接

datasg 段中每个字符串的前 4 个字母转换为大写字母

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
assume cs:codesg, ds:datasg, ss:stacksg

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends

datasg segment
db '1. display'
db '2. brows'
db '3. replace'
db '4. modify'
datasg ends

codesg segment
start:
mov ax, datasg
mov ds, ax

mov bx, 3 ; 第1行,跳过 '1. '
call upper4
mov bx, 13 ; 第2行,跳过 '2. '
call upper4
mov bx, 21 ; 第3行,跳过 '3. '
call upper4
mov bx, 31 ; 第4行,跳过 '4. '
call upper4

mov ax, 4C00h
int 21h

; 子程序:将 [bx] 开始的4个字母改为大写
upper4:
mov cx, 4
next:
mov al, [bx]
cmp al, 'a'
jb skip
cmp al, 'z'
ja skip
sub al, 20h
mov [bx], al
skip:
inc bx
loop next
ret

codesg ends
end start

每条字符串偏移分别是:3、13、21、31(跳过编号和点空格)。

upper4 子程序:处理从 [bx] 开始的 4 个字符。

利用了 cmpsub 判断是否是小写字母。

  1. 字符串操作指令综合(第8章)

    • MOVS/LODS/STOS/CMPS/SCAS 实现功能

编写程序,将 data 段中:

  • 21个年份(每个4字节字符串)
  • 21个年收入(每个4字节 dword)
  • 21个雇员数(每个2字节 word)

分别搬运进 table 段中(每行16字节),并计算人均收入(dword / word → word,单位:千美元)

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
assume cs:codesg, ds:dataseg, es:tablesg

dataseg segment
years db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
sums dd 16,22,382,1356,2390,8000,16000,24486,50065,97479
dd 140417,197514,345980,590827,803530,1183000,1843000
dd 2759000,3753000,4649000,5937000
nums dw 3,7,9,13,28,38,130,220,476,778
dw 1001,1442,2258,2793,4037,5635,8226,11542,14430,15257,17800
dataseg ends

tablesg segment
table db 21 dup (16 dup (0)) ; 21个结构,每个16字节清零初始化
tablesg ends

codesg segment
start:
mov ax, dataseg
mov ds, ax ; 数据段 → DS

mov ax, tablesg
mov es, ax ; 表格段 → ES

xor si, si ; si 用于索引年份
xor di, di ; di = 结构偏移 = ES:table[i * 16]
xor bx, bx ; bx 用于访问 sums 和 nums
mov cx, 21 ; 共 21 年数据

write_loop:
; 拷贝年份 (4 字节)
mov al, [years + si]
mov es:[di], al
mov al, [years + si + 1]
mov es:[di + 1], al
mov al, [years + si + 2]
mov es:[di + 2], al
mov al, [years + si + 3]
mov es:[di + 3], al

; 拷贝收入 (dword, 4 字节)
mov ax, word ptr [sums + bx]
mov es:[di + 4], ax
mov ax, word ptr [sums + bx + 2]
mov es:[di + 6], ax

; 拷贝雇员数 (word, 2 字节)
mov ax, word ptr [nums + bx]
mov es:[di + 8], ax

; 计算人均收入 = dword / word
; dx:ax = 总收入
; cx = 雇员数
mov ax, word ptr [sums + bx]
mov dx, word ptr [sums + bx + 2] ; dx:ax = dword
mov cx, word ptr [nums + bx]
cmp cx, 0
je skip_div ; 避免除 0

div cx ; AX = 商(人均收入),DX = 余数

mov es:[di + 10], ax ; 写入人均收入(word)

skip_div:
add si, 4 ; 下一年年份字符串偏移
add di, 16 ; 下一结构偏移
add bx, 4 ; sums + nums 的索引前进
loop write_loop

; 退出程序
mov ax, 4C00h
int 21h
codesg ends

end start