1. 汇编期末填空题的底层逻辑拆解
1.1 为什么填空题是汇编考试的“分水岭”
汇编语言这门课,平时写代码和考试做题完全是两码事。平时你可以在编辑器里反复调试,报错了就改,改完再跑。但填空题不一样,它考的是你对指令格式、寻址方式、标志位变化、寄存器默认搭配这些细节的精确记忆和条件反射式理解。一个操作数写错,整道题归零,没有中间分。
我观察过很多同学的复习状态:拿着课本从头翻到尾,看到MOV、ADD、JMP这些指令觉得“我都会”,一上考场遇到“下列指令执行后,AX=,CF=”这种题就懵了。根本原因在于,填空题考的不是“你认不认识这条指令”,而是“你能不能在不运行程序的前提下,像CPU一样逐周期地推演结果”。
所以复习填空题的核心策略只有一条:把指令的行为拆解到寄存器、内存、标志位三个维度,形成条件反射。下面我按知识模块拆开讲,每个模块都给出典型的填空考法和推演过程。
1.2 填空题的四大高频知识板块
根据我翻过的多套期末试卷和习题集,汇编填空题的考点分布大致是这样的:
| 知识板块 | 占比 | 典型考法 |
|---|---|---|
| 寻址方式与操作数 | 25% | 给出指令,问源操作数的寻址方式 |
| 指令执行结果 | 35% | 给出寄存器初值,问执行后某寄存器/标志位的值 |
| 伪指令与数据定义 | 20% | 给出数据定义语句,问内存布局或变量值 |
| 中断与子程序 | 20% | 问中断向量表地址、堆栈变化、返回地址 |
这个分布意味着,你把“指令执行结果”这一块吃透,就已经拿到三分之一的分了。而这一块恰恰是最需要动手推演的,光看不动笔基本没用。
2. 寻址方式与操作数:填空题的第一道坎
2.1 七种寻址方式的判定口诀
寻址方式这道题,考试里基本是送分题,但前提是你得能一眼分辨。我总结了一个判定流程,按这个顺序走,不会错:
- 操作数直接写在指令里,没有方括号,没有寄存器名——立即寻址
- 操作数是寄存器名,没有方括号——寄存器寻址
- 操作数是
[BX]、[SI]、[DI]、[BP]这种单个寄存器加方括号——寄存器间接寻址 - 操作数是
[BX+SI]、[BP+DI]这种两个寄存器组合——基址变址寻址 - 操作数是
[BX+5]、[SI-3]这种寄存器加常数——寄存器相对寻址 - 操作数是
[BX+SI+5]这种两个寄存器加常数——相对基址变址寻址 - 操作数是一个变量名,比如
[VAR]或直接写VAR——直接寻址
注意:
[BP]默认段寄存器是SS,其他间接寻址默认是DS。这个细节在填空题里经常考,比如“指令MOV AX,[BP]中,源操作数的物理地址由____和____组合而成”,答案就是SS和BP。
2.2 物理地址计算的填空套路
物理地址 = 段基址 × 16 + 偏移地址。这个公式谁都会,但考试不会直接考公式,它会给你一个具体场景。比如:
“已知 DS=2000H,BX=0100H,SI=0020H,执行指令MOV AL,[BX+SI+10H]后,源操作数的物理地址是____。”
推演过程:偏移地址 = BX + SI + 10H = 0100H + 0020H + 10H = 0130H。物理地址 = 2000H × 16 + 0130H = 20000H + 0130H = 20130H。
这种题的关键是别把段基址忘了乘16。我见过太多人直接写2000H+0130H=2130H,少了一位,整道题白给。
2.3 段超越前缀的填空考法
段超越前缀是填空题的常客。题目通常这样出:
“指令MOV AX,ES:[BX]中,源操作数的段基址来自____寄存器。”
答案显然是ES。但变体来了:“若省略段超越前缀,该操作数的段基址默认来自____。”答案是DS。再变一下:“若指令改为MOV AX,[BP],默认段寄存器是____。”答案是SS。
这类题没有技巧,就是背熟默认搭配表:
| 寻址方式 | 默认段寄存器 |
|---|---|
| [BX]、[SI]、[DI]、[BX+SI]等 | DS |
| [BP]、[BP+SI]、[BP+DI]等 | SS |
| 取指令 | CS |
| 串操作目的串 | ES |
实操心得:考试时遇到段超越的题,先在草稿纸上写下“BX/SI/DI→DS,BP→SS”,然后对照题目逐个核对,比在脑子里空想要可靠得多。
3. 指令执行结果推演:填空题的主战场
3.1 数据传送指令的陷阱
MOV指令看起来最简单,但填空题里埋的坑不少。典型题目:
“执行下列指令序列后,AX=,BX=。”
MOV AX,1234H MOV BX,AX XCHG AX,BX推演:第一条AX=1234H。第二条BX=AX=1234H。第三条交换AX和BX,所以AX=1234H,BX=1234H。结果两个都是1234H。
但如果题目改成:
MOV AX,1234H MOV BX,5678H XCHG AX,BX那交换后AX=5678H,BX=1234H。
XCHG的填空考法通常是“交换后某寄存器的值”,只要你清楚XCHG是双向交换,就不会错。但要注意,XCHG不允许两个操作数都是内存,也不允许使用段寄存器,这些限制有时会以判断改错的形式出现在填空题里。
另一个高频考点是LEA和MOV的区别:
“执行LEA BX,[SI+10H]后,BX=;执行MOV BX,[SI+10H]后,BX=。”
前者BX得到的是偏移地址SI+10H的值,后者BX得到的是该地址处存放的数据。如果SI=0100H,内存01010H处存放的是5678H,那么LEA执行后BX=0110H,MOV执行后BX=5678H。
3.2 算术运算与标志位的联动
这是填空题里最容易丢分的地方。题目通常给你AX、BX的初值,让你算ADD/SUB/INC/DEC之后的结果和标志位。我拿一道经典题来拆:
“已知AX=0FFFFH,执行ADD AX,1后,AX=,CF=,ZF=,SF=,OF=____。”
推演:0FFFFH + 1 = 10000H,但AX只有16位,所以AX=0000H。加法产生了进位,CF=1。结果为0,ZF=1。结果最高位为0,SF=0。作为有符号数看,0FFFFH是-1,加1变成0,没有溢出,OF=0。
这道题的答案是:AX=0000H,CF=1,ZF=1,SF=0,OF=0。
注意:INC和DEC指令不影响CF标志位,这是填空题的经典陷阱。题目会故意把INC和ADD放在一起对比,比如“执行
INC AX和ADD AX,1后,CF的值是否相同?”答案是否定的,INC不影响CF,ADD会影响。
再来看SUB的借位问题:
“已知AX=0001H,执行SUB AX,2后,AX=,CF=。”
推演:1-2=-1,作为无符号数看,需要借位,所以CF=1。AX中的结果是0FFFFH(-1的补码)。
这里有个容易混淆的点:CF在减法中表示借位,而不是“结果为负”。结果为负看SF,借位看CF。填空题经常把这两个标志位放在一起考,让你分别填写。
3.3 逻辑运算与移位指令的填空规律
逻辑运算指令(AND、OR、XOR、NOT、TEST)的填空考法相对直接,但有几个细节:
- AND指令执行后,CF=0,OF=0
- OR指令执行后,CF=0,OF=0
- XOR指令执行后,CF=0,OF=0
- NOT指令不影响任何标志位
- TEST指令和AND一样影响标志位,但不改变操作数
移位指令是另一个高频考点。以SHL为例:
“已知AX=8001H,执行SHL AX,1后,AX=,CF=。”
推演:8001H左移一位,最高位移入CF,最低位补0。8001H = 1000 0000 0000 0001B,左移后变成 0000 0000 0000 0010B = 0002H,CF=1。
如果是SHR(逻辑右移): “已知AX=8001H,执行SHR AX,1后,AX=,CF=。”
8001H右移一位,最低位移入CF,最高位补0。结果是4000H,CF=1。
如果是SAR(算术右移): “已知AX=8001H,执行SAR AX,1后,AX=,CF=。”
算术右移保持符号位不变,最高位补符号位。8001H最高位是1,所以右移后最高位仍然是1,结果是0C000H,CF=1。
循环移位指令ROL、ROR、RCL、RCR的填空考法类似,关键是搞清楚“移出的位去了哪里”和“空出的位补什么”。我整理了一个对照表:
| 指令 | 移出位去向 | 空出位来源 | 是否影响CF |
|---|---|---|---|
| SHL | CF | 0 | 是 |
| SHR | CF | 0 | 是 |
| SAR | CF | 符号位 | 是 |
| ROL | CF和最低位 | 移出位 | 是 |
| ROR | CF和最高位 | 移出位 | 是 |
| RCL | CF | 原CF | 是 |
| RCR | CF | 原CF | 是 |
3.4 串操作指令与重复前缀
串操作指令在填空题里通常考方向标志DF的作用和CX的计数逻辑。典型题目:
“已知DF=0,CX=5,执行REP MOVSB后,CX=,SI和DI分别。”
DF=0表示地址递增,MOVSB每次传送一个字节,SI和DI各加1。重复5次后,CX=0,SI和DI各加了5。
如果DF=1,则SI和DI各减1。如果题目改成REP MOVSW,每次传送一个字,SI和DI各加2(DF=0时)。
实操心得:串操作指令的填空题,先在草稿纸上画一个表格,列出每次迭代后CX、SI、DI的值,迭代两三次就能看出规律,比在脑子里空想准确得多。
4. 伪指令与数据定义:内存布局的填空考法
4.1 数据定义语句的内存分配
伪指令部分的填空题,核心是考你“变量在内存里占几个字节,值是什么”。先看基本规则:
| 伪指令 | 每个元素占用字节数 |
|---|---|
| DB | 1 |
| DW | 2 |
| DD | 4 |
| DQ | 8 |
| DT | 10 |
题目通常这样出:
“有下列数据定义:
DATA SEGMENT A DB 10,20,30 B DW 1000H,2000H C DD 12345678H DATA ENDS问:变量A占____字节,变量B占____字节,变量C占____字节,DATA段总共占____字节。”
答案:A占3字节,B占4字节,C占4字节,总共11字节。
但考试不会这么直白,它会问“变量B的第一个字节的值是____”。这里就涉及到小端序的问题。B DW 1000H,在内存中低字节在前,所以第一个字节是00H,第二个字节是10H。如果问“B的第二个字节”,答案是10H。
4.2 DUP操作符的填空技巧
DUP是填空题的常客,因为它能制造“看起来很多其实很简单”的题目:
“BUF DB 5 DUP(0,1,2)执行后,BUF占____字节,BUF+3处的值是____。”
5 DUP(0,1,2)表示重复5次括号里的内容,每次3个字节,总共15字节。内存布局是:0,1,2,0,1,2,0,1,2,0,1,2,0,1,2。BUF+3是第四个字节,从0开始数:BUF+0=0,BUF+1=1,BUF+2=2,BUF+3=0。所以答案是15和0。
嵌套DUP更绕一些:
“BUF DB 2 DUP(3 DUP(1), 2)执行后,BUF占____字节。”
先算内层:3 DUP(1)占3字节,加上2占1字节,每组4字节。外层2 DUP,总共8字节。内存布局是:1,1,1,2,1,1,1,2。
4.3 地址计数器$的填空应用
$在汇编中表示当前地址计数器的值,填空题里经常用它来制造“相对地址”的题目:
“有下列数据定义:
DATA SEGMENT ORG 10H A DB 1,2,3 B DW $, $ DATA ENDS问:B的第一个字的值是____,第二个字的值是____。”
ORG 10H表示从偏移地址10H开始。A占3字节,所以A的地址是10H、11H、12H。B从13H开始。第一个$表示当前地址,即13H,所以B的第一个字是0013H。第一个$占2字节,第二个$的当前地址是15H,所以B的第二个字是0015H。
这种题的关键是逐字节跟踪地址计数器的变化,不能跳步。
5. 中断与子程序:堆栈和向量表的填空
5.1 中断向量表的地址计算
中断向量表固定在内存最低的1KB空间,每个中断向量占4字节,前2字节是偏移地址,后2字节是段基址。填空题通常考地址计算:
“中断类型码为20H的中断向量存放在地址____到____的单元中。”
计算:20H × 4 = 80H,所以存放地址是00080H到00083H。其中00080H和00081H存放偏移地址,00082H和00083H存放段基址。
如果题目问“从中断向量表中取出偏移地址后,应放入____寄存器”,答案是IP;取出段基址后放入CS。
5.2 子程序调用与堆栈变化
CALL和RET指令的填空题,核心是堆栈指针SP的变化和返回地址的存放:
“执行CALL SUB1指令前,SP=1000H,CS=2000H,IP=0100H。执行CALL后,SP=,堆栈顶部存放的内容是。”
CALL指令将下一条指令的地址(CS和IP)压栈。压栈顺序是先压CS,再压IP。所以SP先减2,再减2,总共减4,SP=0FFCH。堆栈顶部(SP指向的位置)存放的是IP的值,即0100H加上CALL指令的长度。如果CALL是3字节指令,那么返回地址的IP=0103H。
注意:压栈顺序是CS先入栈还是IP先入栈,不同教材可能表述不同,但实际执行时,CALL指令先将CS压栈,再将IP压栈。RET指令则相反,先弹出IP,再弹出CS。填空题如果问“堆栈顶部存放的是____”,通常指的是SP当前指向的单元,即最后压入的内容。
5.3 子程序返回值的传递
子程序通过寄存器或堆栈传递参数的填空题,通常考“执行完子程序后,某寄存器的值是什么”。比如:
“主程序将参数10和20分别放入AX和BX,调用子程序后,AX=____。”
如果子程序是ADD AX,BX,那么AX=30。如果子程序是MUL BX,那么AX=200(10×20)。这类题的关键是跟踪子程序内部对寄存器的修改,不要被主程序的初值迷惑。
6. 常见问题与排查技巧实录
6.1 填空题高频错误速查表
| 错误类型 | 典型表现 | 纠正方法 |
|---|---|---|
| 段基址忘乘16 | 物理地址少一位 | 默念“段基址左移4位” |
| INC/DEC影响CF | 误填CF=1 | 记住INC/DEC不影响CF |
| 小端序搞反 | 高低字节颠倒 | 画内存图,低地址存低字节 |
| 移位指令补位错误 | SAR补0而非符号位 | 区分逻辑移位和算术移位 |
| 堆栈压入顺序错误 | CS和IP颠倒 | 记住CALL先压CS后压IP |
| DUP计算漏乘 | 总字节数算少 | 先算内层再算外层 |
6.2 考场上的推演技巧
填空题最大的敌人是“想当然”。我见过太多人看到MOV AX,[BX]就填“把BX的值给AX”,实际上是把BX指向的内存单元的值给AX。避免这种错误的方法只有一个:每道题都在草稿纸上写出推演过程,哪怕是最简单的题。
具体操作:拿到题目后,先写下所有已知寄存器的初值,然后逐条指令模拟执行,每执行一条就更新一次寄存器状态。遇到标志位,单独列一栏。遇到内存操作,画出内存布局图。这个过程看起来慢,但准确率会大幅提升。
实操心得:考试时带两支笔,一支写正常推演,一支专门标红“易错点”。比如看到INC就在旁边写“不影响CF”,看到[BP]就写“SS”,看到DUP就写“先内后外”。这些红字提醒能帮你避开80%的陷阱。
6.3 复习阶段的刷题策略
填空题的复习不能只靠看,必须动手写。我的建议是:
- 第一遍:按知识板块刷题,每个板块集中做20-30道,做完立刻对答案,错的题标记出来。
- 第二遍:只做错题,重点分析错误原因,是概念不清还是粗心。
- 第三遍:限时模拟,每道题控制在1分钟内,训练条件反射。
- 考前一周:把易错点整理成一页纸,每天看一遍。
刷题材料优先用课后习题和历年真题,网上的题库质量参差不齐,有些答案本身就是错的。如果发现某道题的答案和你的推演不一致,先怀疑答案,再怀疑自己,用调试器验证一下。
7. 从填空题到真理解:汇编复习的延伸价值
填空题看似是应试的产物,但它训练的能力在实际开发中非常有用。当你在调试一段汇编代码时,脑子里推演寄存器变化的能力,和做填空题时是一模一样的。你能快速判断“这条指令执行后AX会变成什么”,就能快速定位bug。
我在实际工作中遇到过一个问题:一段中断处理程序偶尔会崩溃,但复现概率很低。后来我逐条指令推演堆栈变化,发现是中断嵌套时SP没有正确恢复。这个问题如果靠调试器单步跟踪,可能要花几个小时,但用填空题的推演方法,十分钟就定位了。
所以,别把填空题当成纯粹的应试任务。它逼你养成的“逐指令推演”习惯,是汇编层面调试能力的基石。把这个习惯保持下去,期末考试只是顺带的事。
最后分享一个我当年复习时的小技巧:把常用的指令和标志位变化做成一张A4纸的速查表,贴在书桌前,每天早晚各看一遍。一周之后,那些原本需要想一下的知识点,就变成了条件反射。填空题考的就是条件反射,你练到了,分就是你的。