ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

IBM-PC汇编习题答案全解析:从数制转换到寻址方式避坑指南

IBM-PC汇编习题答案全解析:从数制转换到寻址方式避坑指南

简介:《IBM-PC汇编语言程序设计》配套习题参考答案,由沈美明、温冬婵教材内容整理,适合正在学习汇编语言基础的学生与自学者对照练习。答案覆盖十进制、二进制与十六进制互换,八位补码表示与溢出标志、进位标志判断,位运算,ASCII码及字符串处理等核心知识点,并包含实模式下段地址与偏移地址求物理地址的题目,以逐题解答形式呈现。具体例题涉及将369转换为二进制与十六进制、用补码计算负八十五加七十六并分析标志位、读取指定存储单元内容等,便于验证思路并加深对机器级数据表示和存储器寻址的理解。资源为单个Word文档,压缩包仅五百二十四KB,编排紧凑,可直接按章节翻阅。目前已有115人学习,适合需要系统完成课后习题、巩固IBM-PC汇编基础的用户下载使用。

1. 一份可以照着练的 IBM-PC 汇编习题答案:它到底能帮你解决什么

很多人在学《IBM-PC 汇编语言程序设计》时都卡在同一道坎上:教材里的概念每一条都看得懂,但一落到课后习题上就不知道从哪下手。尤其是寻址方式、物理地址计算、标志位变化这类题目,答案对不对心里完全没底,自己算完了也没有核对依据。这份文档其实是把教材里从第一章到第三章的习题参考答案完整整理了一份,覆盖数制转换、补码运算、物理地址计算、七种寻址方式、数据传送、算术运算、位操作、串指令这些最核心的知识点,每条都给了计算过程和最终结果。

它的价值不止于"对答案"。第一章的进制转换和补码计算,能顺带帮你把数据表示的地基补牢;第三章的寻址方式题目,几乎是把汇编里最容易混的那几种寻址罗列了一遍,每一题都有明确的物理地址推导;串指令部分的程序片段也能直接作为模板,在写字符串处理代码时改改就能用。适合的人群很明确:正在学 8086 实模式汇编的学生、准备应付考试但缺乏练习题源的人,以及工作中需要临时补汇编基础、想快速过一遍核心考点的开发者。

有一点要说在前面:这文档是参考解答,不是官方标准答案。个别表述在今天的汇编器里需要适应,比如立即数写法、段跨越前缀的处理,我后面会专门把这些差异点挑出来讲。

2. 数制转换与补码运算:先把“汇编眼中的数”搞明白

2.1 为什么汇编代码里到处都是十六进制和补码

汇编语言操作的对象本质是寄存器和内存里的二进制位,而我们书写代码时不可能真去写一长串 0 和 1。十六进制是二进制的紧凑表示,一位十六进制数恰好对应四位二进制位,换算不需要计算器。文档习题 1.1 到 1.5 集中练的就是这个基本功。

以 369 转十六进制为例,文档给出的答案是 369=1 0111 0001B=171H。拆开看,1 0111 0001 从右往左每四位分组是 0001、0111、0001,对应十六进制数位 1、7、1,所以结果是 171H。反过来,要把 0FFH 这样的十六进制数转十进制,只需按位权展开:15×16+15=255。这里的关键是“四位一组”的分组习惯,很多人从右边开始分组就会漏掉前导零,导致转换结果错位。

提示:在汇编源码里,以字母开头的十六进制数必须在前面补 0,比如 0FFH。文档习题 2.5 里特意标注“如 16 进制数的最高位为字母,则应在其前加一个 0”,这就是为了照顾汇编器的词法规则——否则 FFH 会被当成标识符。

真正容易翻车的是补码运算。习题 1.5 要求用 8 位二进制补码计算带符号数加减,并同时标出 CF 和 OF。看第 (4) 小题:85-(-76)。85 的补码是 0101 0101B,-76 的补码要先算 76=0100 1100B,取反加一得 1011 0100B。做减法时,CPU 实际执行的是“被减数加上减数的补码再做取反加一”这套逻辑,反映到指令层面就变成了加法。计算过程如下:

; 85 - (-76) ; 85 的补码:0101 0101B ; -76 的补码:1011 0100B(取反加一得到) ; 执行 ADD:0101 0101 + 1011 0100(实际上做的是 85 + 76 的补码形式), ; 但这里注意:CPU 对 SUB 指令是“目标 - 源”,内部用加法器完成。 ; 按文档的推演:0101 0101B - 1011 0100B = 0101 0101B + 0100 1100B = 1010 0001B = 0A1H ; OF=1 说明两个正数相加得到负数,产生带符号溢出。

这段推演想说明的是:补码减法没有所谓的“借位”概念,统一走加法器。写代码时你可以手动把减数取反加一来核对结果,但 CPU 硬件在 SUB 指令内部也是这么干的。真正要盯住的是 OF 标志——它反映的是带符号数溢出,而 CF 反映的是无符号数的进位/借位。习题答案里每个结果后面都标了 CF 和 OF,这是全卷最容易丢分的地方,后面避坑章节我会专门展开。

2.2 ASCII 码:字符和数据之间的那道桥

习题 1.7 和 1.8 练的是 ASCII 码识别与字符串编码。4FH 对应字符 O,59H 对应字符 Y,这几个是常见考点。更实用的是 1.8 的字符串“For example,This is a number 3692.”的完整 ASCII 序列,文档里把每个字符的十六进制码都列了出来,比如 F 是 46H,o 是 6FH,空格是 20H,逗号是 2CH。

字符串在汇编里的存储方式没有特殊之处,就是连续的字节。有一个细节容易被忽略:文档里 0AH 和 0DH 出现的时机,也就是回车换行。你在终端里看到的多行输出,在内存里就是这两个字符穿插在字符串中。所以在写串输出程序前,我一般会先把字符串的 ASCII 序列逐字节列一遍,确认每个控制字符的位置,再决定是用INT 21H的单字符输出还是整串输出。

2.3 从数制到标志位:这一章的复习脉络

梳理一下这章的复习顺序,可以按“进制转换 → 补码表示 → ASCII → 标志位初识”四步走。进制转换靠手算足够,补码部分建议把 8 位和 16 位的正负边界值都练熟:8 位补码的范围是 -128 到 127,16 位是 -32768 到 32767。文档习题 1.6 里 D8H 看作无符号数是 216,看作带符号数是 -40,这个转换就是“用补码的符号位判断正负,负数的绝对值 = 按位取反加一”,多练几题就能形成条件反射。

3. 物理地址与存储器寻址:实模式下的一切都靠“段:偏移”说话

3.1 物理地址的换算公式与常见误区

实模式下物理地址 = 段地址×16 + 偏移地址。习题 2.4 里给了三组段:偏移,3017:000A、3015:002A、3010:007A,答案都是 3017AH,这个结果不奇怪,因为三组地址本来就指向同一个物理位置。这个题目真正想考的是两点:一是 16 位段寄存器左移四位再相加;二是同一个物理地址可以由多组逻辑地址映射。

第 2.5 题更实际一点:已知 (CS)=0A7F0H,(IP)=2B40H,求程序第一个字的物理地址。计算就是 0A7F0H×16 + 2B40H = 0A7F00H + 2B40H = 0AAA40H。注意段地址 0A7F0H 这个写法,它本身就占了 20 位地址中的高 16 位,左移一位十六进制位就是在后面补一个 0。这里的陷阱在于写代码时用MOV AX, 0A7F0H直接给段寄存器赋值是没问题的,但如果你用MOV DS, 0A7F0H就会报错——段寄存器不能从立即数直接装载,要先送通用寄存器。

; 正确装载段寄存器的方式 MOV AX, 0A7F0H MOV DS, AX ; 段寄存器只能从通用寄存器或内存装载

3.2 MOV 指令的边界:内存操作数和立即数

第三章开头有一组题专门考察 MOV 指令在不同寻址方式下执行后 AX 的内容,题目 3.4 给出的内存数据分布是经典的考点。已知 (DS)=2000H,(BX)=0100H,(SI)=0002H,内存里 (20100H)=12H、(20101H)=34H、(20102H)=56H、(20103H)=78H。执行MOV AX, [BX]之后 AX 应该是多少?

这里的关键是 x86 的小端存储。一个 16 位字在内存里是低字节在前、高字节在后,所以 [BX] 指向的 20100H 处取两位字节,得到 34H 放在 AH、12H 放在 AL,结果 AX=3412H。同理,MOV AX, [BX][SI]取 20102H 和 20103H 两个字节,得到 AX=7856H。文档答案完全符合这个规律。

这一块最容易犯的错误是惯性思维,总觉得“内存里数字怎么排,寄存器里就怎么排”。实际上寄存器的 AX 在指令视角下是“数值”,不在内存里,所以 12H 存到 AL 还是 AH 取决于读取的方向。做题时我建议在纸上画出内存地址从小到大的格子,把每个字节填进去,再按小端读出,基本不会错。

3.3 段跨越前缀与 ES、SS 的适用场景

习题 3.8 第 (7) 小题很典型:MOV AX, [BP]的物理地址是 SS×16+BP=15010H,而不是 DS 段。8086 规定 BP 默认搭配 SS,SP 也搭配 SS,BX、SI、DI 默认搭配 DS。这个规则很多初学者会漏掉,尤其 BP 在函数栈帧里用得频繁,如果忘了段跨越前缀,数据就取错地方了。

MOV AX, [BP] ; 默认段 SS,物理地址 = SS:BP MOV AX, [BX] ; 默认段 DS,物理地址 = DS:BX MOV AX, ES:[BX] ; 显式段跨越,物理地址 = ES:BX

习题 3.6 给出了一个完整的三步装载外部变量的过程,先用MOV BX, [2000H]拿偏移地址,再用MOV AX, [2000H+2]取段值放到 ES,最后MOV AX, ES:[BX]完成真正取值。这段代码的编写思路值得记下来:当你要访问的数据不在当前 DS 段内、而段值又保存在内存里时,就得先把段值取到 ES 或 DS,再通过寄存器间接寻址访问。如果直接在一条指令里写MOV AX, ES:[BX],而 ES 还没设置正确,结果自然不对。这是实模式下跨段操作的固定套路。

4. 寻址方式与指令语义:从“会写指令”到“算对结果”

4.1 七种寻址方式的兵力部署

第三章前几题几乎就是寻址方式的“全量体检”。习题 3.1 把立即寻址、直接寻址、寄存器寻址、寄存器间接寻址、寄存器相对寻址、基址变址寻址、相对基址变址寻址全部过了一遍。已知 (BX)=637DH,(SI)=2A9BH,D=7237H,基址变址寻址的 EA=(BX)+(SI)=8E18H,相对基址变址寻址 EA=(BX)+(SI)+D=1004FH。这个 1004FH 超过了 16 位偏移的表示范围(FFFFH),按文档的说法“最高进位位丢失,因此 EA=004FH”,这正是 16 位地址回绕的实际表现。

这里我建议把所有寻址方式整理成一张记忆表,做题时对照着套:

寻址方式有效地址 EA 计算公式示例指令
立即寻址操作数在指令中,无 EAMOV AX, 1234H
寄存器寻址无 EA,操作数在寄存器MOV AX, BX
直接寻址EA = 位移量MOV AX, [1200H]
寄存器间接EA = (BX/SI/DI/BP)MOV AX, [BX]
寄存器相对EA = (BX/SI/DI/BP) + DMOV AX, VAL[BX]
基址变址EA = (BX/BP) + (SI/DI)MOV AX, [BX][SI]
相对基址变址EA = (BX/BP) + (SI/DI) + DMOV AX, VAL[BX][SI]

这张表覆盖了 8086 实模式下所有访存方式。做题时先确定是哪种寻址,再代公式,最后换算物理地址,三步走基本不会乱。

4.2 MOV 与 LEA:取内容还是取地址

习题 3.10 是一个经典对比:TABLE 是 0032H 单元的符号名,里面存着 1234H。MOV AX, TABLE执行后 AX=1234H,而LEA AX, TABLE执行后 AX=0032H。这个对比看似简单,但能检验你是否真正理解“变量名”在汇编里的本质。

MASM/TASM 里,直接写变量名TABLE在 MOV 指令里表示“该地址单元的内容”,而在 LEA 里表示“该地址本身”。LEA 指令在编译后实际上是加载有效地址到寄存器,不访问内存。一个常见的误用是把 LEA 当成 MOV 的等价物去取值,写LEA AX, [BX]和MOV AX, [BX]在功能上有差别:前者给的是 BX 的值(也就是地址),后者给的是该地址指向的内存内容。文档在 3.10 题答案里弄了一组对照,值得反复看。

提示:如果要取数组元素的地址而不是元素值,用 LEA 比MOV BX, OFFSET 变量更灵活,LEA 支持基址变址,而 OFFSET 只能直接编码符号地址。

4.3 算术与位运算指令的默认操作数位置

习题 3.17 到 3.20 集中考察乘除法、移位、求补等指令的隐含操作数约定。比如乘法指令,8 位乘法默认 AL 是被乘数、结果在 AX;16 位乘法默认 AX 是被乘数、结果在 DX:AX。除法指令的约定反过来:8 位除法被除数在 AX,商在 AL、余数在 AH;16 位除法被除数在 DX:AX,商在 AX、余数在 DX。这些约定不写在指令里,但你必须知道。

看习题 3.20 第 (3) 小题的指令序列:

STC ; CF=1 MOV BX, DATAX ; BX = DATAX 的字内容 ADC BX, DATAY ; BX = BX + DATAY + CF

文档解释说这是“两个字数据之和再加 1”。ADC 指令带进位加,STC 就是人为把进位位置 1,用来模拟多字节加法中的低位进位。在很多多精度运算代码里,你会看到CLC; ADC; ADC这样的排列组合,进位传递就是靠 CF 完成的。做这类题时,我习惯先写下每条指令执行后的寄存器值和标志位,一行一行的对答案,比直接背结论来得稳。

4.4 指向数据的指针在题目里的体现

习题 3.6 和 3.14 都涉及到“指针变量”的取用。3.6 的内存图显示 1B00:2000H 处存放着 0FF10H 和 8000H,这两个字分别是一个变量的偏移地址和段地址。取这个变量你需要三条指令:先取偏移地址到 BX,再取段地址到 ES,最后用MOV AX, ES:[BX]完成读取。这个过程在高级语言里就是一个指针解引用,但在汇编里每步都要手动完成。做题时注意:偏移地址和段地址的存放顺序也是低地址放偏移、高地址放段值,别取反了。

5. 汇编初学者的避坑指南:五个最容易丢分的实操细节

这一章整理的踩坑记录,一部分来自我自己初学时的翻车经历,一部分来自带教新手时常犯的典型错误,每一条都能在这份习题答案里找到对应场景。

5.1 十六进制立即数没补前导零

现象:写MOV AX, FFH或MOV BX, A000H时汇编器直接报错,行内提示非法字符。原因:以字母 A~F 开头的十六进制数会被词法分析器识别为标识符,FFH 被当成一个名字。解决:所有以字母开头的十六进制立即数强制写成0FFH、0A000H。这个规则在文档 2.5 题里专门标注过,实际写代码时最容易犯。

5.2 直接在 MOV 里给段寄存器赋立即数

现象:MOV DS, 0B800H编译报错,错误提示大概是“immediate mode not allowed”。原因:8086 指令集规定段寄存器的装载只能通过通用寄存器或内存,立即数必须先到 AX 等寄存器。解决:改成两行,先MOV AX, 0B800H再MOV DS, AX。文档的所有程序段都是这个套路,照抄就不会错。

5.3 分不清 CF 和 OF 的判定边界

现象:某题中两个正数相加得到负数,答案里 CF=0、OF=1,很多人在试卷上把两个标志位写反。原因:CF 管无符号数的进位/借位,看最高位有没有向更高位进位;OF 管带符号数的溢出,看运算结果是否超出补码表示范围。两个标志位的判定依据完全不同。解决:做题时先用无符号视角判 CF,再用带符号视角判 OF。比如 85-(-76)=0A1H,8 位结果是 0A1H=161,无符号视角 85-152 不够减有借位所以 CF=0(CPU 里 SUB 的 CF 是 1 表示借位),带符号视角两个正数相加得到负数,OF=1。

5.4 除法前忘了扩展被除数符号位

现象:IDIV指令执行后直接产生除零中断或商溢出,程序崩溃。原因:16 位除法要求被除数在 DX:AX,如果你只给 AX 赋值而没处理 DX 的符号扩展,除法结果会完全不可控。解决:有符号除法前用CWD(Word 转 Double word)或CWDE扩展符号位,无符号除法前把 DX 清零。文档 3.20 第 (7) 题做双字除以字时直接MOV DX, DATAX+2,这就是手动装配被除数,另一边 MOV AX 取低字。

5.5 串指令的方向标志 DF 忘了初始化

现象:REP MOVSB复制的字符串每次结果都反了,或者复制到一半就偏了。原因:DF=0 时 SI/DI 自动递增,DF=1 时自动递减,如果不显式设置,默认状态并不是固定的。解决:在串操作前总是执行CLD(DF=0)或STD(DF=1),并且配合LEA时注意起始地址的加减。文档 3.29 第 (2) 问里“从右到左传送”就同时用了STD和ADD SI, 20-1,这两步缺一不可。我自己后来写串指令前都会强制加一行 CLD,哪怕默认是 0——这是最便宜的安全保险。

6. 用 DEBUG 逐条验证答案:把纸面推演变成寄存器里的铁证

习题答案是静态的计算结果,但汇编程序的真实行为由机器码在 CPU 上执行决定。验证答案最直接的手段不是重新手算,而是用 DEBUG 的 T(跟踪)命令单步执行,看每条指令执行后寄存器和标志位的变化是否和答案一致。

; 假设要验证 MOV AX, 1234H 之后的 CF、SF、ZF、OF 状态 ; 在 DEBUG 里这样操作: ; - E 0100 B8 34 12 ; 写入 MOV AX, 1234H 的机器码 ; - R ; 确认各寄存器初始状态 ; - T ; 单步执行,观察 AX 变化和标志位

DEBUG 加载程序后,用 R 命令显示寄存器,用 T 命令逐条执行指令,用 D 命令查看内存字节。把文档第三章习题里的指令序列照着输入,你会发现标志位的变化和答案完全吻合,比如 3.19 题里ADD AX, 7FFFH后 SF=0、ZF=0、OF=0、CF=1。这种验证方式最大的好处是能让你看到答案里没写出来的细节:比如 AF 辅助进位标志的值、每条指令执行后的 IP 变化。

我自己在梳理第三章汇编题目时用过一个笨办法:把每题的操作数组合列成一张表,每条指令执行后记下 AX、CF、SF、ZF、OF 的实测值,再和文档答案逐行核对。有一次遇到SAL AX, 1和SAR AX, 1在标志位上的差异,答案里 OF 一个标 1 一个标 0,我最初以为是排版错误,用 DEBUG 一跑才发现:SAL 的 OF 要看最高位是否变化,SAR 的 OF 恒为 0——因为算术右移把符号位移入高位,结果的正负性不会改变。从那以后,凡是涉及移位和标志位组合的题,我都会先在 DEBUG 里走一遍再落笔,这个习惯帮我避开过不少“概念上正确但细节里见鬼”的坑。

如果你是纯纸质学习派,也可以用另一个方法:把题目里给的内存数据布局画成 8×8 的格子,每个格子里填一个字节的十六进制值,然后模拟 MOV、ADD、SUB 等指令的执行,手动更新寄存器和标志位,每一行都和答案比对。这个过程本质上就是在复刻 DEBUG 做的事。无论用工具还是手算,核心原则是同一个——汇编的每一行代码都有确定的机器行为,任何模糊的“大概是”都要用执行结果来收服。希望这份习题答案和学习路径能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进