bt

汇编期末冲刺:2022 B 卷逐题拆解与三个课程项目

· 16 min read · 汇编语言 , 8086 , 期末复习 , 真题

⚠︎ This post has no English translation yet — showing the original Chinese text.

这篇讲考试本身:一道题一道题拆,错在哪、怎么改、给几分,最后用三个项目把所有零散的东西串起来。

系列里的另外三篇:为什么学汇编 讲入门和路线,环境与 14 课实战 讲怎么跑代码,王爽完整教程 按教材章节查知识点。

源码在仓库 汇编/ 目录里,每道编程题都有能直接编译运行的标准答案。


目录

  1. 这份卷子的结构
  2. 分析简答题:六类错误
  3. 标志位与 ORG:两道需要动手算的题
  4. 程序分析题:填空的套路
  5. 宏展开:一个宏两种命运
  6. 三道程序设计题
  7. 三个课程项目
  8. 复习清单

1. 这份卷子的结构

2022-2023 学年 B 卷是能找到的最新一份,题型和现在最接近:

大题分值考什么
一、分析简答40找指令错误、伪操作错误、移位、数据定义、标志位、ORG
二、程序分析30填空、读运行结果、宏展开
三、程序设计30写完整程序

一个很实在的观察:前 70 分几乎全是”能不能一眼看出问题”,真正的完整编程只占 30 分。所以复习的重心不该是背程序,而是把常见的错误形式认熟。

下面所有标 [实测] 的地方,都是把题里的错代码真的敲进 MASM 跑出来的报错原文,不是书上抄的。


2. 分析简答题:六类错误

2.1 内存 + 内存

ADD VAR1, VAR2 ; ✗ error A2052: Improper operand type

8086 的加法电路一头必须接寄存器,硬件上就没有”内存 + 内存”这条指令。改法是借一个寄存器中转:

mov ax, VAR1
add ax, VAR2
mov VAR1, ax ; 注意:ADD 不会自动写回内存

得分点:很多同学只写到 add ax, VAR2 就停了,忘了结果还得搬回去。

2.2 类型不匹配

SUB AL, VAR1 ; ✗ warning A4031: Operand types must match

AL 是 8 位,VAR1 是 16 位的字变量,宽度对不上。有意思的是 MASM 只给警告不给错误,但考卷要的就是这个错误写法。改法:sub al, byte ptr VAR1,或者把 AL 换成 AX。

2.3 跳转目标搞混

三个错误指向同一件事——跳转的目标必须是地址,不能是数据:

JMP LAB[SI] ; ✗ error A2044: Must be associated with data
JNZ VAR1 ; ✗ error A2045: Must be associated with code
JMP NEAR LAB ; ✗ error A2028: Operator expected
  • JMP LAB[SI] 把代码标号和变址寻址搅在一起。LAB 是代码段地址,[SI] 默认按数据段寻址,汇编器不知道你要哪段。勉强能编过的写法是 jmp word ptr cs:LAB[SI],但考试写标准答案。
  • JNZ VAR1 更直接:VAR1 里装的是数据,不是地址。条件转移的目标只能是标号。
  • JMP NEAR LAB 是语法问题,NEAR 不能裸用,得写 JMP NEAR PTR LAB,或者直接 JMP LAB。

2.4 伪操作错误

DATA_SEG SEG ; ✗ error A2105: Expected: instruction or directive

SEG 是取段地址运算符(mov ax, seg data 里的那个),不是伪指令。定义段用 SEGMENT。

MYDATA SEGMENT/DATA ; ✗ error A2105: Expected: align, combine, or 'class'

SEGMENT 后面只能跟对齐、组合、类别三个字段,/DATA 什么都不是。

END 的位置是最常犯错的一个:

MAIN_PROC PROC FAR
...
END MAIN_PROC ; ✗ 错在这
MAIN_PROC ENDP

END 是整个源程序的最后一条语句,它一出现,后面的东西全被无视,于是过程没闭合、段也没闭合。报错原文是:

Open segments: CODE
Open procedures: MYP

正确顺序:

MAIN_PROC ENDP
END MAIN_PROC ; END 后面跟"程序入口标号"

2.5 移位当乘法,要补一句

MOV CL, 3
SHL AX, CL ; ×8
SHL BL, CL ; ×8
SHL DH, CL ; ×8

这三条能完成任务,但必须说清条件才能拿满分:移位是无检测的乘法,乘积超出位数范围时高位直接丢失。比如 BL = 40H,×8 = 200H 装不进 8 位,结果就是错的。

另外 8086 上移位数大于 1 时必须放 CL,不能写 SHL AX, 3。

2.6 MOV 和 OFFSET:搬内容还是搬地址

MOV AX, TABLE ; AX = 0FF00H ← TABLE 里的内容
MOV AX, OFFSET TABLE ; AX = 0100H ← TABLE 自己的偏移

一句话:一个搬内容,一个搬地址。LEA AX, TABLE 和后者等价。

配套的还有 WORD PTR 的用法:

CODELIST DB 1,7,8,3,2
MOV SI, WORD PTR CODELIST ; SI = 0701H(小端序)

CODELIST 本身是字节型,不加 WORD PTR 汇编器不让你一次取两个字节。


3. 标志位与 ORG:两道需要动手算的题

3.1 标志位连线题

初始 BX = 0FFFFH,逐条执行后填 SF / ZF / CF:

指令SFZFCF原因
XOR BX, BX010结果为 0;XOR 恒清 CF
ADD BX, 8001H100结果 8001H,最高位为 1;未进位
SAR BX, 11018001H → C000H,移出的 bit0 进 CF

背一句口诀就够应付全部移位题:移出去的位进 CF。SHL、SHR、SAR、ROL 都遵守这条。

3.2 ORG 与内存分配

DATA SEGMENT
ORG $+20H
DA1 DB 10H DUP(2,3)
DA2 DW 10H DUP(2,3)
ORG $+20H
BUF DD DA1,DA2
DATA ENDS

把 $ 当成”当前偏移”,一步步挪:

  1. ORG $+20H:$ 从 0 → 20H
  2. DA1 DB 10H DUP(2,3):每个重复单元是 2 字节(2 和 3),重复 10H 次 → 32 字节,$ → 40H
  3. DA2 DW 10H DUP(2,3):每个重复单元是 2 个 DW = 4 字节,重复 10H 次 → 64 字节,$ → 80H
  4. ORG $+20H:$ → A0H

答案:DA1 占 32 字节,DA2 占 64 字节,BUF 的偏移是 A0H。

题目本身不长,但最容易错的是第 3 步——DW 每个元素 2 字节,一个重复单元里有两个 DW,所以是 4 字节不是 2 字节。这种题光看说明书没用,得手算几遍。


4. 程序分析题:填空的套路

4.1 求数组平均值

FLD DW 10,-20,30,-60,-71,80,79,56
MOV CX, 8
XOR AX, AX
(1) MOV SI, OFFSET FLD ← 空①
R1: (2) ADD AX, [SI] ← 空②
ADD SI, 2
LOOP R1
MOV CL, 8
IDIV CL

填空题的判据全在上下文里:

  • 循环体用 [SI] 且 ADD SI, 2 → SI 是数组指针,但没人初始化它 → 空①必须是 MOV SI, OFFSET FLD。
  • 循环体只有指针自增没有累加 → 空②是 ADD AX, [SI]。

还有一个容易被忽略的细节:数据里有负数,所以用的是 IDIV(带符号除法)而不是 DIV。题目特意这么选。

4.2 两数交换

要求大者进 NUM,小者进 NUM+1,两者都是8 位带符号数:

MOV AL, NUM
CMP AL, NUM+1
(1) JGE DONE
(2) XCHG AL, NUM+1
MOV NUM, AL
DONE:

两个得分点:

  • 题目强调”带符号” → 必须用 JGE/JL 这组,用 JAE/JB 直接错。这是整道题的题眼。
  • 相等时谁在谁的位置都对,所以用 JGE(大于等于)而不是 JG。

4.3 读循环结果

MOV AX,0 / MOV BX,1 / MOV CX,5
LP: ADD AX,BX
ADD BX,2
LOOP LP

手动展开:9 次累加项是 1、3、5、7、9,和是 25。但还有两个不用算就能拿分的地方:

  • BX 最后又加了 2,变成 11(不是 9)。
  • CX 循环完是 0。

判卷时这两处经常被漏掉,写了才不丢分。


5. 宏展开:一个宏两种命运

MSG MACRO P1, P2, P3
IN&P1 P2 P3
ENDM
K=1
MSG %K, DB, 'MY NAME'
MSG C, AX

展开结果:

IN1 DB 'MY NAME' ; P1=1,& 拼成数据名
INC AX ; P1=C,& 拼成指令助记符

这道题的妙处在于同一个宏,P1 传不同的值,& 拼接出来一个是数据定义、一个是指令。

% 的作用也要说清:%K 把 K=1 先算出数值再传参。不写 %,传过去的是字面 K,拼出来是 INK,名字对不上直接报错。

怎么验证:看列表文件。MASM 会把你”粘贴”出来的每一行都列出来:

masm L14MAC, L14MAC.OBJ, L14MAC.LST;

打开 .lst 搜 inc ax,能看见展开的痕迹。这个技巧比背结论管用,宏题不再靠想象。

两个小提醒:

  • 仓库 汇编/LAB/L14LIS.TXT 是一份已经生成好的样本,直接打开就能看到展开过程。
  • 你自己生成的列表文件会把源码注释的中文一起写进去,按 GBK 编码,在 VS Code 里显示成乱码。别管乱码——汇编出来的指令、地址、机器码全是纯 ASCII,直接搜 inc ax 就能找到。想看中文就用记事本以 ANSI 编码打开。

6. 三道程序设计题

三道题都在仓库 汇编/EXAM/ 里有可直接编译的答案。

6.1 统计 AX 中 “1” 的个数

思路是 ROL 把最高位转进 CF,看 CF 计数,转 16 次。

坑:CX 通常用来存计数,但这里结果要计数、循环也要计数,两者都抢 CX。解决办法是结果放 CX,循环次数改用 BX。

6.2 求带符号数 X 的绝对值

CMP AL, 0
JNS done ; 非负就跳过
NEG AL
done:

考点藏在边界上:X = 80H(-128)时 NEG 之后还是 80H,因为 +128 装不进 8 位有符号数。老师爱追问这个。

6.3 1~50 中偶数累加

小步长直接走偶数:

mov ax, 0
mov bx, 2
sum_loop:
add ax, bx
add bx, 2
cmp bx, 50
jbe sum_loop

结果 650。关键在于 650 > 255,所以存结果的变量必须用 DW 不能用 DB——这种细节是拉开分差的地方。


7. 三个课程项目

单个知识点会了,下一步是把它们串起来。三个项目的难度是递增的。

7.1 P1:整数计算器

CALC: 12+34
= 46
CALC: 7*6
= 42
CALC: 100/7
= 14 rem 2
CALC: 3-10
= -7

输入一整行,程序自己解析两个数和运算符。用到的新招式:

  • 0Ah 缓冲输入:缓冲区格式是 db 最大长度, 实际长度, 内容...,实际长度在 [buf+1]。这个格式记不住就会一直读到垃圾。
  • 16 位乘除:mul bp 的积在 DX:AX,div bp 的商在 AX、余数在 DX。
  • 负数显示:先判断符号,打 -,再 neg 取绝对值。

有个细节值得注意:3-10 和 5-12 输出都是 -7,这正好验证了负数处理逻辑是对的。另外 1000*1000 和 5/0 这种边界输入,程序里都做了检查。

7.2 P2:成绩管理

choice> 1
score (1-100): 88
score (1-100): 73
score (1-100): 95
score (1-100): 101
invalid! only 1-100
score (1-100): 0
invalid! only 1-100
score (1-100):
OK, total = 3
choice> 4
--- stats ---
max = 95 min = 73 avg = 85.3

这是课程设计级别的综合程序,把菜单、数组、排序、统计全放进一个程序里。

数组下标和地址的换算值得单独看一眼:scores + i*2。8086 不支持 [bx+i*2] 这种乘过之后再寻址的写法,得先算好偏移再加。

平均分 85.3 是 256/3 = 85.33 保留一位,余数单独算小数位。

7.3 P3:电子钟

19:31:07
Press ESC to quit

这个项目把前面几课全用上了:

  • L10 的显存直写,把黄色提示固定在最后一行
  • L11 的 int 21h 2Ch 取时间 + 补零打印
  • L12 的非阻塞按键检测(这里是 DOS 的 0Bh/08h 版本)

最值得学的是它的主循环结构:

每一轮心跳:
a. 查键盘,ESC 就退出
b. 取时间
c. 秒变了才重画

“秒变了才重画” 用了一个叫”脏检查”的模式:把上次显示的秒存起来,只有变化了才动笔。不做这个,屏幕每秒闪几十次。这个套路以后写任何菜单或游戏都会用到。

另外注意光标定位:用 int 10h 2 号功能把光标移到固定位置再打印,而不是每次都刷整屏。


8. 复习清单

按投入产出比排序:

题型考什么在哪练
指令找错内存-内存不行、类型要匹配、跳转目标必须是标号L05CMP + 本文第 2 节
伪操作SEGMENT/ENDP/END 的顺序,SEG 是运算符L13DIR L01HI
数据定义ORG / DUP / $ / 偏移计算L13DIR
标志位SHL/SAR 后的 CF/SF/ZFL08NUM
程序填空指针初始化 + 累加 + 有符号/无符号跳转的选择L04BX L05CMP
读循环结果循环结束后 CX 归零、BX 多走一步L03LOOP L06SORT
宏MACRO / & 拼接 / % 展开L14MAC
程序设计位统计、绝对值、条件累加EXAM/Z22B1~3
上机题U/T/P/R/D/GEXAM/DEBUG练习.md

两个复习建议:

第一,边做题边跑。 觉得”这条指令应该报错”,就真敲进 MASM 看一眼。报错原文记在脑子里,考场上比推理快得多。本文所有 [实测] 标记的地方都是这么来的。

第二,每道编程题先自己写,再看答案。 汇编/EXAM/ 里的答案程序都能直接编译运行,写完之后对比输出。看得懂和写得出,中间隔着一条河。