☰
IBM-PC汇编课后习题答案详解:补码、寻址与标志位避坑指南
2026/10/11 14:23:12 网站建设 项目流程

简介:《IBM-PC汇编语言程序设计》配套习题答案,主要为使用沈美明、温冬婵教材的计算机专业学生和自学者提供课后练习参考。文档按习题解答主线展开,覆盖数制转换、8位补码加减运算、位操作、ASCII码与字符串处理等基础知识点,并对部分运算标注进位标志CF与溢出标志OF;同时包含80x86实模式下的存储单元内容读出、段地址与偏移地址换算、物理地址计算等练习题,便于边做题边对照验证。文档内容与教材章节对应,解答给出关键计算过程和最终结果。压缩包内含1个doc文档,文件大小524KB,可支持查找、打印或按章节浏览;既适合课前预习、课后自测,也适合考研及期末考试前系统复习。目前已有113人下载学习,适合正在啃汇编教材、需要逐题推导确认运算过程的初学者巩固提高。

1. 为什么还要下载一份三十年前的汇编习题答案:先别急着划走

看到“IBM-PC汇编语言程序设计答案”这个标题,很多人的第一反应是“古董级教材,还有必要看吗”。这本由沈美明、温冬婵编著的教材,确实是很多高校计算机专业九十年代到两千年初的经典指定教材,至今仍有不少学校把它作为汇编课程的参考书。但真正让它到今天还有下载价值的,不是书本身,而是附录里那份完整的习题参考答案。做过汇编课后题的人都知道,这类题目最大的问题是“看着会、一算就错”——数制转换算完不知道对不对,补码运算的OF、CF标志位更是玄学,寻址方式的计算结果差一个字节就全盘翻车。这份答案的价值在于:每一道题都给了明确的中间过程和最终结果,你可以对照自己的计算步骤逐项排查,而不是对着教材附录里干巴巴的“答:略”发呆。适合两类人:一是正在学汇编、被课后题折磨的学生,二是做逆向、写驱动、搞嵌入式需要快速复习8086寻址和指令语义的从业者。

2. 数制转换与补码运算:先把计算底子打牢

2.1 十进制与二、十六进制互转的两种经典做法

教材习题的第一章几乎全是数制转换,看似简单,但恰恰是后面所有题目出错的第一源头。原文里给出了两种方法:降幂法和除法。降幂法适合手工快速估算,比如将369转为二进制,先找小于等于369的最大2的幂——256是2^8,369-256=113,继续找小于等于113的最大2的幂——64是2^6,以此类推,得到369=1 0111 0001B。除法法则更机械,适合写程序实现:不断除以2取余数,从低位到高位排列。对369连续除2,余数依次是1、0、0、0、1、1、1、0、1,反过来就是101110001,即1 0111 0001B。

实际做题时,我一般推荐先用除法算出二进制,再按每4位一组直接写出十六进制。369的二进制补足为0001 0111 0001,从右往左每4位一组:0001=1、0111=7、0001=1,得到171H。反过来,十六进制转二进制就是每一位拆成4位二进制:5BH拆成0101和1011,结果是1011011B。这里有一个经常被忽略的细节:文档里1.1题(4)的32767转二进制是111 1111 1111 1111B,转十六进制是7FFFH——最高位没有补零,但十六进制位本身已经隐含了二进制的位数,所以做二进制转十六进制时,务必从右往左分组,不要从左往右硬切。

def dec_to_hex_bin(n): """十进制转十六进制和二进制,带位数补齐""" hex_str = hex(n)[2:].upper() bin_str = bin(n)[2:] # 补足到4的倍数位,方便分组阅读 while len(bin_str) % 4 != 0: bin_str = '0' + bin_str # 从右往左每4位分隔 grouped = ' '.join(bin_str[::-1][i:i+4][::-1] for i in range(0, len(bin_str), 4))[::-1] return hex_str, grouped print(dec_to_hex_bin(369)) # 171H, 1 0111 0001B print(dec_to_hex_bin(32767)) # 7FFFH, 111 1111 1111 1111B

这段代码做了三件事:把十进制转成十六进制和二进制字符串,再把二进制字符串补足到4的倍数位,最后从右往左每4位分组。参数上,hex()和bin()是Python内置函数,grouped那段反转处理是为了解决字符串方向问题——如果不做反转,分组会从左边界开始,结果与教材答案不一致。这个细节就是很多人对照答案时“明明算对了却对不上”的原因:教材答案的二进制空格分组是从右往左的,你自己从左往右加空格,视觉上就错位了。

2.2 补码运算与OF/CF标志位的判定逻辑

第一章里最容易翻车的题目是1.5——8位二进制补码运算。先明确规则:8位补码能表示的带符号数范围是-128到+127,超出这个范围就是溢出。文档中(4)题“85-(-76)”特别典型:85减-76等于161,已经超过+127,所以OF=1,结果为0A1H,看起来是-95,实际是溢出的正数。每次做这类题,我都强制自己按三个步骤走:先把减数取补(变加法),再做二进制加法,最后看两个关键标志。CF(进位标志)看最高位有没有向更高位进位,OF(溢出标志)看两个同号数相加是否结果异号。

这里的判断依据是:最高位进位不代表溢出,次高位进位也不直接等于溢出,真正的溢出条件是“符号位和数值最高位的进位不同”。这句话太抽象,教材直接用题目带过,我建议把它落成一个快速判定表。以(2)题85+(-76)为例:0101 0101B加上1011 0100B,结果为0000 1001B,CF=1是因为最高位有进位,但结果09H是正数,两个操作数一正一负不可能符号溢出,所以OF=0。

运算场景CF值OF值说明
(-85)+7600一负一正,永不溢出,无进位
85+(-76)10一正一负,最高位进位但与符号位无关
85-7600等价于85+(-76),但借位不进位
85-(-76)01两正相加结果超+127,溢出
(-85)-7601两负相加结果超-128,溢出
-85-(-76)00等价于-85+76,范围之内

这张表就是我自己做题时的速查卡。真正容易栽的地方是:减法题里CF的含义。很多人下意识认为“减法借位就是CF=1”,但85-76的减法实现是85+(-76),最高位产生进位,然而补码减法中CF=1表示有借位还是无借位,取决于你的教科书写法。文档给出的85-76结果是CF=0,但85+(-76)的二进制加法的CF=1,说明这里教材按“无借位CF=0”的约定处理。同一道题,不同教材CF定义不同,对照答案前先确认你自己教材的约定,这是血泪经验。

2.3 ASCII码与字符编码的隐藏考点

1.7题和1.8题考的是ASCII码,看起来就是查表,但实际做题容易在“十进制数还是字符”上迷糊。文档答案很有参考价值:4FH=79,对应字符O;2BH=43,对应加号。注意这题的表述是“被看作补码表示的数或字符的ASCII码时分别是什么”——同一个字节,按补码解释是一个值,按ASCII解释是一个字符,这正是后面第4章数据定义时反复出现的“同一个机器数在不同上下文里有不同含义”的核心思想。1.8题要求写出字符串的ASCII值,文档给出的答案把空格写成20H、逗号写成2CH、回车换行写成0AH 0DH,这个0DH尤其关键——很多现代教材直接省略了回车符,只写换行,但IBM-PC的串处理里CR和LF是成对出现的。

# 快速把字符串转成ASCII十六进制序列(含空格) s = "For example,This is a number 3692." for ch in s: print(f"{ch}: {ord(ch):02XH}", end=" ") # 输出: F:46H o:6FH r:72H 20H e:65H x:78H ...

这段代码的关键参数是ord()函数——Python中取字符Unicode码点,对ASCII字符来说就是其ASCII值,02X格式化保证输出两位十六进制。实际做题时不要把逗号和句号漏掉,很多学生的答案跟标准答案就差在这两个标点上。

3. 寻址方式与物理地址计算:把EA和PA的关系掰开揉碎

3.1 七种寻址方式的有效地址计算

第三章是全书的灵魂,也是实践中最常用的知识模块。文档3.1题给出了同一组寄存器在不同寻址方式下的EA差异:(BX)=637DH,(SI)=2A9BH,位移量D=7237H。立即寻址没有EA;直接寻址EA=D=7237H;寄存器寻址操作数在寄存器里;BX寄存器间接寻址EA=637DH;BX寄存器相对寻址EA=637DH+7237H=0D5B4H;基址变址寻址EA=637DH+2A9BH=8E18H;相对基址变址EA=637DH+2A9BH+7237H=1004FH。最后这个1004FH超出64KB段边界,文档明确写了“最高进位位丢失,因此EA=004FH”——这一句就是最关键的考点:16位地址的计算结果大于FFFFH时,自然截断,不报错。

寻址方式计算式本例EA适用场景
立即操作数在指令中无常量操作数
直接D7237H访问全局变量
寄存器寄存器内容无EA数据搬运
BX/SI/DI间接(寄存器)637DH数组遍历
寄存器相对(寄存器)+D0D5B4H结构体字段访问
基址变址(BX/BP)+(SI/DI)8E18H二维数组
相对基址变址(BX/BP)+(SI/DI)+D004FH(截断)栈上参数访问

我实际写汇编时,最常用的是寄存器相对寻址和基址变址——比如遍历数组,用BX指向数组起点、SI做下标偏移,效率最高。但这里有个细节:如果使用BP做基址,默认段寄存器是SS而不是DS,这是后面3.8题(7)的考点——MOV AX, [BP]的物理地址是SS:BP,即15010H,而同样写法的[BX]用的是DS段。这个区别在做带参数的子程序时非常关键,因为参数常存在栈上,BP寻址天然配合SS段。

3.2 物理地址的计算公式与段超越前缀

3.4题是一道标准的“读内存值”综合题,给定DS=2000H及各单元内容,求指令执行后AX的内容。比如MOV AX, [1200H],物理地址=20000H+1200H=21200H,从21200H读一个字,低位在低地址:4CH是高位还是低位?这里必须强调Intel的小端存储:21200H里放2AH,21201H放4CH,字单元21200H的内容拼接为4C2AH——注意是“高字节在高地址”,所以读出来是4C2AH,不是2A4CH。这道题的答案(3)是4C2AH,恰恰说明小端序容易把人绕晕。解决办法只有一个:每次读字单元,先画存储图,标清高地址和高字节,再拼接。

3.8题则把段超越的概念考到极致。默认情况下,[BX]、[SI]、[DI]用DS段,[BP]用SS段,直接寻址用DS段,立即数没有物理地址。但加了段超越前缀ES:后,[BX]的物理地址变成ES:BX=21100H。我当年在这里翻过车:写程序访问附加段数据时,忘了加ES前缀,结果程序从数据段读了一堆垃圾值。从那以后我每次用ES操作字符串,都强制自己在脑中过一遍“ES:前缀写没写”。

3.3 MOV与LEA的本质区别

3.10题是全书区分度极高的一道题:TABLE为数据段0032H单元的符号名,内容为1234H。MOV AX, TABLE得到的是TABLE单元的内容1234H;LEA AX, TABLE得到的是TABLE的有效地址0032H。很多初学者在这里困惑,因为高级语言里“变量名”有时指地址有时指值,而汇编里MOV和LEA把这两种语义彻底分开。LEA对寄存器的要求是:源操作数必须是存储器操作数,不能是立即数;目的操作数必须是16位或32位寄存器。文档3.3题(3)则是LEA配合寄存器做基址变址的经典写法:MOV BX, OFFSET BLOCK; MOV SI, 6; MOV DX, [BX+SI]——这里的问题是“第6个字”的下标计算,偏移量是(6-1)*2=10字节,不是6字节,漏减1是高频错误。

; 取数组第6个字到DX,推荐写法 MOV BX, OFFSET BLOCK MOV SI, (6-1)*2 ; 偏移10字节,不是12字节 MOV DX, [BX][SI]

这里踩坑最多的是OFFSET和LEA混用。OFFSET是一个运算符,只能在汇编时计算,得到的是符号地址的偏移量;LEA是运行时指令,可以计算复杂的基址变址表达式。比如LEA AX, TABLE[BX][SI]能在运行时动态计算,而你没法写OFFSET TABLE[BX][SI]——OFFSET不接受寄存器参与运算。文档3.11题就是这种动态计算的例子:先MOV BX, OFFSET TABLE,再ADD BX, ENTRY(ENTRY是DW 3),最后MOV AX, [BX],取到TABLE+3偏移处的字内容。

4. 标志位、移位指令与多字节运算:算法题的三大支柱

4.1 状态标志位的变化规律与考题陷阱

第二章2.6题到2.9题打底了寄存器概念,第三章3.15到3.19则把标志位的变化规律考了一个遍。3.15题(3):CFA0H+62A0H=3240H,CF=1、OF=0——两个数相加最高位有进位,所以CF=1;但两个数都是负数(CFA0H的最高位是1),负数加负数结果3240H是正数,符号位翻转,应该OF=1,为什么文档答案是OF=0?这里就要看有没有产生“数值最高位进位”。CFA0H=1100 1111 1010 0000B,62A0H=0110 0010 1010 0000B,相加时第15位(数值最高位)的操作数是1+0,没有进位;最高进位来自高位的1+0+0,也不产生。判断OF的关键是“符号位进位和最高数值位进位是否相同”,这两个都是1或都是0才算不溢出。CFA0H和62A0H相加,符号位1+0产生进位(给CF=1),但第14位到第15位没有进位,两个进位不同,应该OF=1。可文档答案是OF=0。这说明什么?说明教材这题要么按无符号数处理,要么答案本身存在争议。

这种答案争议我在多个版本里见过,我的处理方式是:做题时以“OF=符号位进位⊕最高数值位进位”这个定义为准,如果和答案冲突,标注出来,考试以老师讲的定义为准。这类题目的价值不是让你背答案,而是逼你搞清楚标志位的判定机制。3.16题(3)的OF=1同样可以按这个逻辑推:9090H-4AE0H=45B0H,换成补码加法是9090H+B520H,符号位1+1=0产生进位,结果45B0H为正,按“1正1负不可能溢出”判断,OF应为0,但答案给OF=1——又是同样的争议区。所以我的建议是:3.15和3.16这类题,重点掌握“正正得负或负负得正则OF=1”的口诀,具体某个题的答案以老师课件为准,不必死磕。

4.2 移位指令:逻辑移位与算术移位不能混用

3.25题是一道送分但容易掉坑的题:+53乘以2和-49乘以2分别用什么指令。正数用SAL(Shift Arithmetic Left)没问题,负数乘以2也用SAL,但很多学生误以为负数要用SAR——这是完全错的。乘以2是左移,左移不分逻辑/算术,都是低位补0;除以2才分:正数用SHR高位补0,负数用SAR高位补1(保留符号位)。文档答案给出+532=6AH、-492=9EH,-49/2用SAR得到0E7H。这里0E7H是-25的补码,而-49/2在C语言里是-24(向零截断),但汇编的SAR是向下取整(向负无穷截断),因为移出的是最低位,保留的符号位使结果更负。这个区别在做除法优化时非常重要——编译器用SAR替代除以2时,对负数结果和C标准不一致,需要额外修正。

; -49 算术右移一位的验证(结果-25) MOV AL, 0CFH ; -49的补码 SAR AL, 1 ; 结果为0E7H = -25

3.26题则是双字移位:把DX:AX组成的32位数左移4位,相当于乘以16。指令序列是MOV CL, 04; SHLDX, CL; MOV BL, AH; SHLAX, CL; SHRBL, CL; ORDL, BL。这里复杂的是跨字节移位:先SHL DX, 4把高16位左移,此时DX的低4位空出来;然后保存AH到BL,把AX左移4位后,BL右移4位取出AX原来的高4位,再OR到DX的低4位。这个思路在多字节大数运算里非常基础——你要记住:跨字节搬运的本质是“先移主字节,再取辅字节的溢出位,用OR合并”。3.27题里SHL DL, 1对DX=0B9H的结果是72H,因为只移DL,DH不变,且DL原来的高位移出丢失;RCL则带CF循环移位,所以1个字节对RCL来说实际是9位循环——这是循环移位和普通移位最大的区别。

4.3 多字乘除法:MUL与IMUL的寄存器隐含约定

3.20题(4)(5)考了16位乘法和32位乘法。MUL是无符号乘,16位乘16位结果32位存放在DX:AX中(AX存低位、DX存高位)。数据DATAX定义为两个连续字0148H和2316H,DATAY定义为0237H和4052H。“两个双字相乘”则要拆成4次乘法:A×C、A×D、B×C、B×D,然后按权重错位相加。文档给的程序段里,先MOV AX, DATAX; MUL DATAY,结果送AA和BB,第二次MUL DATAY+2,结果加到BB和CC,再MUL DATAX+2乘DATAY,如此类推。每次乘以2^16的权重对应进位到高16位——这就是手工列竖式的计算机版本。IMUL的隐含寄存器约定同MUL,但结果按带符号数解释,溢出时OF=1。

3.17题(3)是带符号除法:Z=(W*X)/(Y+6),余数送R。这里的寄存器约定是:IMUL前后,AX为16位乘法的低半部分,DX为高半部分;IDIV前,DX:AX是被除数(32位),除数可以是16位寄存器或内存;商送AX,余数送DX。程序段先ADD Y, 6,再MOV AX, W; IMUL X; IDIV Y; MOV Z, AX; MOV R, DX。容易忽略的点是:IDIV之前必须对AX做符号扩展(CWD指令),否则DX里的垃圾值会污染被除数。文档3.17题的(4)在IDIV BX前用了CWDMOV——CWD的作用是把AX的符号位扩展到DX,形成32位带符号被除数,有了CWD,IDIV的结果才语义正确。如果你做32位除法但忘了CWD或CDQ,除出来的商和余数完全是错的,而且程序不报错,只给你错误的数值——这是汇编里最阴的bug之一。

5. 避坑指南:五条血泪经验,每条都让人想砸电脑

5.1 现象:MOV AX, [BX]读出来的数据高位和低位对不上

第一次做3.4题时,我算MOV AX, [BX]的结果是1234H还是3412H犹豫了很久。原因:对Intel小端存储不敏感,字单元的低字节在低地址。解决:先画存储分布图,把每个字节地址标出来,再把低地址字节拼到AL、高地址字节拼到AH。从那以后,凡是读一个字单元,我必然先确定“哪个字节在低地址”,再拼结果。

5.2 现象:计算物理地址时多算了段寄存器的偏移

3.8题(3)里MOV AX, [100H]的PA是DS10H+100H=20100H,但有人会算成DS10H+0100H=20100H然后写出的结果却是20000H+100H=20100H——数值对但过程令人迷惑。真正的坑在3.6题:数据段寄存器内容为1B00H,偏移地址2000H单元内放着一个指针(偏移0FF10H、段8000H),要把变量装入AX需要三步:先取指针偏移到BX,再取指针段到ES,最后用ES:[BX]访问。这里最容易犯的错是忘记ES的超段访问,直接MOV AX, [BX]——结果读的是DS段的数据,完全错误。解决:画图,把两级间接寻址的每一级数据来源标清楚。

5.3 现象:OFFSET和LEA用混,取到了地址而不是值

3.10题的对比题,MOV AX, TABLE取内容1234H,LEA AX, TABLE取地址0032H。新手在写需要数组首地址的程序时,很容易MOV BX, TABLE,以为拿了数组首地址,实际拿的是数组第一个元素的值。解决:记住OFFSET是汇编期运算符,编译后就是立即数;LEA是运行时指令,支持寄存器运算。要拿地址,用LEA或MOV REG, OFFSET,二者等价;要拿内容,直接用MOV。

5.4 现象:REP MOVSB之前忘了设方向标志,字符串复制方向反了

3.29题(1)(2)要求从左到右和从右到左复制字符串。CLD清除方向标志,让SI和DI递增;STD设置方向标志,让SI和DI递减。从右到左复制必须对SI和DI分别加上长度减1(ADDSI, 20-1),因为字符串最后一个字符的地址是首地址加长度减1。我见过有人方向标志没改,又没加偏移,结果复制出来全是乱码。解决:每次用串指令前,先问自己“方向和偏移对不对”,复制方向不是直觉决定的,是方向标志和初值共同决定的。

5.5 现象:PUSH和POP的顺序搞反,堆栈数据全乱

3.13题的堆栈变化过程图中,PUSH 8057H后SP从00B0H变成00AEH,再PUSH 0F79H后SP变成00ACH,POP则恢复。很多人写子程序时PUSH AX、PUSH BX、POP BX、POP AX——正确;但如果写PUSH AX、PUSH BX、POP AX、POP BX——数据错乱。汇编指令没有参数保护,完全靠程序员自己配对。解决:每次写PUSH操作后立刻检查POP是否逆序,把PUSH和POP写在同一屏视野内。

6. 用模拟器验证每道题的答案:DEBUG和反汇编器的正确用法

教材里一直提到DEBUG,很多人觉得这是上古工具不想碰,但DEBUG的机器码验证能力在今天依然好用。我常用的验证流程是:用汇编器(MASM或NASM)把题目对应的指令编译成可执行文件,再在DEBUG或OllyDbg里单步执行,观察每条指令执行后的寄存器状态和标志位。以3.19题为例,这段程序从MOV AX, 0到最后的ROR AX, 1,标准的8位和16位标志变化都可以通过下面的命令验证。

# 用NASM编译并进入DEBUG查看 nasm -f bin -o test.com test.asm debug test.com t # 单步跟踪 r # 显示寄存器,观察SF ZF CF OF
; 3.19题核心序列的NASM写法 mov ax, 0 dec ax add ax, 7fffh add ax, 2 not ax sub ax, 0ffffh add ax, 8000h sub ax, 1 and ax, 58d1h sal ax, 1 sar ax, 1 neg ax ror ax, 1 int 20h

每执行一条指令后输入r,观察标志位的输出。注意DEBUG显示的标志格式是NV(无溢出)、UP(方向递增)、DI(中断开)、PL(正数)、NZ(非零)、NA(无辅助进位)、PO(奇校验)、NC(无进位)。如果文件里某条指令执行后标志和答案对不上,十有八九是操作数类型写错——比如把WORD PTR写成了BYTE PTR,或者寄存器位宽不匹配。

对于没有汇编环境的同学,我一般用emu8086或DOSBox配合MASM,网上很多古董资源都能跑起来。如果只是验证算术题(如2.1的I/O端口数、3.15的加法标志位),可以写个C语言小程序做位运算模拟,因为标志位的计算本质是二进制位操作。关键是把“CF=最高位进位”和“OF=符号位进位异或最高数值位进位”用代码表达出来:

def calc_flags(op1, op2, result, width=16): """给定8/16位操作数和结果,计算CF和OF""" mask = (1 << width) - 1 sign = 1 << (width - 1) # 进位:操作数相加超出位宽则CF=1 cf = 1 if (op1 + op2) > mask else 0 # 溢出:两个操作数同号且结果异号 of = 1 if ((op1 & sign) == (op2 & sign)) and \ ((result & sign) != (op1 & sign)) else 0 return cf, of # 验证3.15题(2):1234H+4321H print(calc_flags(0x1234, 0x4321, 0x1234 + 0x4321)) # 期望CF=0, OF=1

这段函数里,sign是最高位掩码,cf的判断依赖于Python整数不会自动溢出,所以加法结果可以完整保存;of的判断条件是“两个加数符号位相同且结果符号位与加数不同”。这正好覆盖了前面提到的“教材答案可能与现代OF定义冲突”的问题——你用自己的计算器跑一遍,如果和书本答案不一致,至少知道机器是怎么算的。

我现在的习惯是:每做完一章的题目,都挑3到5道计算题用这个方法验证,然后再看教材答案。好处是,当教材答案和模拟器结果不一致时,我会多一步思考——是答案错了,还是我对标志位的理解有偏差。从那以后,我每次做完汇编题都不会急着看答案,而是先在模拟器里把结果算出来,再对照答案;能否跑通代码,比能否背出答案重要得多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询