资讯动态

计算机系统基础第三章习题解析:寻址方式、指令格式与栈帧实战

发布时间:2026/10/15 6:51:39 来源:尧图企业网站定制
简介这份资源是袁春风《计算机系统基础第二版》第三章课后习题的参考答案面向正在学习计算机组成与系统底层原理的高校学生及考研复习者帮助解决指令系统、寻址方式与机器级表示等章节练习中的疑难。压缩包内仅含1个PDF文件约3.15MB内容按题号逐题给出解答涵盖寻址方式判定、指令格式错误分析、数据类型转换表、函数参数栈布局、算术与逻辑运算标志位变化以及程序执行后寄存器取值等关键知识点。目前已有7731人学习下载说明其在校内课程与备考群体中认可度较高。读者可借助这份答案对照自查理清后缀w、b、l与操作数组合的对应关系掌握movsbl、movzbl等转换指令的用法理解add、sub、or、test对OF、SF、ZF、CF的影响并熟悉xptr、yptr、zptr在栈中的偏移计算从而深化对机器级代码执行过程的理解为后续汇编与系统编程打下基础。1. 从一道“寻址方式”题说起这份第三章答案到底能帮你解决什么如果你正在啃《计算机系统基础》第三章大概率会在某个深夜被“基址比例变址偏移”这种寻址方式组合卡住。教材里讲得清楚但一到课后习题尤其是涉及 IA-32 指令格式、操作数类型匹配、标志位变化的题目光靠翻书很难确认自己到底对不对。这份第三章课后习题参考答案覆盖的就是习题 3 到习题 34 的完整解析从寻址方式判断、指令格式纠错到机器级表示、函数栈帧分析、结构体对齐全部有逐题拆解。它适合两类人一类是正在跟这门课、需要核对答案和理清推导过程的学生另一类是想通过习题反向理解 IA-32 指令系统和程序机器级表示的开发者。答案里不只是给结果很多题都附了二进制运算过程、寄存器变化推演和汇编指令注释比如第 8 题对add、sub、or、test指令执行后 OF、SF、ZF、CF 的逐位分析第 18 题对pushl、leave、ret指令执行后 ESP、EBP 变化的完整追踪。这些内容在教材正文里往往一笔带过但做题时恰恰是判断依据。需要先说明的是这份答案是配合教材第三章的习题使用的不是独立教程。如果你还没看过寻址方式、数据传送指令、算术逻辑运算指令、栈帧结构这几节直接看答案会比较吃力。但如果你已经过了一遍教材、正在做题验证这份解析的颗粒度足够让你把每个选项为什么对、为什么错都搞清楚。2. 寻址方式与指令格式纠错从后缀到操作数匹配的逐题拆解2.1 后缀字母与操作数类型的对应关系习题 3 和习题 4 集中考察的是 IA-32 汇编指令中长度后缀与操作数类型的匹配。后缀b、w、l分别对应字节、字、长字这个规则本身不难但题目里往往把源操作数和目的操作数的寻址方式混在一起需要同时判断后缀是否正确、源和目的是否匹配。以习题 3 第1题为例答案是“后缀w源基址比例变址偏移目寄存器”。这里的关键是后缀w说明操作数是 16 位源操作数用了基址加比例变址再加偏移的复杂寻址方式目的操作数是寄存器。判断时先看后缀再看源和目的的操作数形式最后确认两者长度是否一致。习题 4 则是反向纠错给出一段有问题的指令让你指出错误原因。比如第1题“源操作数是立即数 0xFF需在前面加$”这是 ATT 格式汇编的硬性规定立即数必须加$前缀否则会被当成内存地址。第2题“源操作数是 16 位而长度后缀是字节b不一致”这是典型的长度不匹配错误。第3题“目的操作数不能是立即数寻址”因为立即数没有存储地址不能作为写入目标。这些错误在手工写汇编或读反汇编代码时经常遇到。我自己的习惯是看到一条指令先扫后缀再扫源和目的的操作数类型最后看有没有语法层面的问题。这个顺序能避免大部分低级错误。2.2 数据传送指令的机器级表示习题 5 给出了一张表要求填写不同源类型和目的类型组合下的机器级表示。题目涉及char、int、short、unsigned等类型之间的转换对应的指令有movsbl、movb、movl、movswl、movzbl等。这里的关键是区分符号扩展和零扩展。movsbl是“传字节并符号扩展到长字”movzbl是“传字节并零扩展到长字”。题目中char到int的转换用movsbl因为char在 IA-32 中默认是有符号的unsigned char到int的转换用movzbl因为无符号数高位补零。实际写代码时这种类型转换的机器级表示直接影响到程序的行为。比如把一个char类型的负数赋给int如果编译器用了零扩展而不是符号扩展结果就会完全错误。习题 5 的表格本质上是在训练你建立“C 类型转换 → 汇编指令选择”的映射关系。2.3 函数参数传递与栈帧定位习题 6 考察的是函数调用时参数在栈中的位置。题目给出xptr、yptr、zptr三个指针参数要求写出它们对应的存储单元地址。答案是R[ebp]8、R[ebp]12、R[ebp]16这是 IA-32 栈帧的标准布局ebp指向栈底ebp4是返回地址ebp8开始是第一个参数之后每个参数占 4 字节。题目还要求根据汇编代码反推 C 语言函数体。给出的汇编逻辑是先把*xptr、*yptr、*zptr分别读到临时变量然后做循环赋值。对应的 C 代码是void func(int *xptr, int *yptr, int *zptr) { int tempx *xptr; int tempy *yptr; int tempz *zptr; *yptr tempx; *zptr tempy; *xptr tempz; }这段代码的逻辑是把三个指针指向的值轮换x的值给yy的值给zz的值给x。反推时关键是跟踪每个临时变量从哪个内存地址读取、写回到哪个内存地址。这种题目在考试里经常出现实际调试时如果遇到栈帧错位也需要用同样的方法去核对ebp偏移和参数顺序。3. 算术逻辑运算与标志位二进制推演和条件转移的实战分析3.1 加减运算对 OF、SF、ZF、CF 的影响习题 8 是这一章里计算量最大的题目之一涉及add、sub、or、test、imul、dec等多条指令执行后寄存器内容和标志位的变化。题目要求逐位写出二进制运算过程并判断 OF、SF、ZF、CF 的值。以第1题为例指令是add操作为0x00000080 0xFFFFFFF0。二进制展开后0000 0000 0000 0000 0000 0000 1000 0000 1111 1111 1111 1111 1111 1111 1111 0000 1 0000 0000 0000 0000 0000 0000 0111 0000结果是0x00000070最高位进位为 1所以 CF1。结果不为零ZF0。结果最高位为 0SF0。两个操作数符号不同不会溢出OF0。第2题是减法0x00000010 - 0x80000008。减法在硬件里转成加法实现0x00000010 0x7FFFFFF8。结果是0x80000008最高位为 1SF1结果不为零ZF0有借位CF1两个操作数符号不同但结果符号与被减数不同OF1。这些标志位的判断规则在教材表 3.5 里有总结但做题时容易搞混的是 CF 和 OF 的区别CF 是无符号溢出的进位/借位OF 是有符号溢出的标志。第5题里imul指令执行后结果溢出但乘法指令不影响标志位所以 OF 不会被置 1。这一点在考试里经常设坑实际写汇编时也要注意不能依赖乘法指令后的标志位来判断溢出。3.2 条件转移指令的偏移量计算习题 11 考察的是条件转移指令的目标地址计算。IA-32 的条件转移指令采用相对转移方式目标地址 当前指令地址 指令长度 偏移量。题目给出了je、jb、jle、jmp等指令的机器代码要求计算转移目标地址。以第1题为例je指令的机器代码是7408H操作码74占 1 字节偏移量08占 1 字节指令总长 2 字节。当前指令地址是0x804838c所以目标地址 0x804838c 2 0x8 0x8048396。第4题是jmp指令机器代码E9H后面跟 4 字节偏移量因为是小端方式偏移量为FFFFFF00H即-256。当前指令地址0x804829b目标地址 0x804829b 0xffffff00 0x804819b。这类题目的关键是先确定指令长度再确定偏移量的字节序最后做加法。实际调试时如果遇到跳转地址不对也可以用同样的方法反推偏移量是否正确。3.3 循环与条件判断的汇编还原习题 12 和习题 13 要求根据汇编代码还原 C 语言的if-else和循环结构。习题 12 的汇编代码里先判断p是否为 0再判断x是否小于 0最后执行*p x。对应的 C 代码是嵌套的ifvoid comp(char x, int *p) { if (p ! 0) if (x 0) *p x; }习题 13 的汇编代码涉及x -100、x 16等多个分支对应的 C 代码是if-else if-else结构。还原时要注意汇编里的条件转移指令顺序对应 C 代码里的条件判断顺序跳转目标对应代码块的开始位置。习题 14 更复杂一些涉及循环、除法、乘法、递减和条件跳转。汇编代码里用idiv做带符号除法用imulw做乘法用decw递减循环变量用testw和jle判断循环是否结束。还原出的 C 代码是一个while循环循环体里做x * (y % k)的累乘循环变量k每次减 1直到k 0或y k时退出。这类题目的训练价值在于让你习惯从汇编的角度理解 C 代码的控制流。实际工作中如果遇到性能问题需要看反汇编时这种“汇编 ↔ C”的对应关系就是基本功。4. 结构体对齐、数组寻址与栈溢出那些容易翻车的细节4.1 结构体成员偏移与对齐规则习题 27 到习题 29 集中考察结构体对齐。题目给出了多个结构体定义要求计算每个成员的偏移量和结构体总大小。IA-32 和 x86-64 的对齐规则不同Windows 和 Linux 的对齐规则也有差异。以习题 27 第1题为例结构体S1的成员顺序是short s、char c、int i、double d。在 IA-32 Linux 下short占 2 字节char占 1 字节int占 4 字节double占 8 字节但按 4 字节对齐。成员偏移分别是s在 0c在 2i在 4d在 8。总大小 12 字节。第6题的结构体S6成员顺序是char c、short s、double d。c在 0s需要 2 字节对齐所以在 2d需要 4 字节对齐所以在 4但double本身占 8 字节所以d实际占 4 到 11总大小 12 字节不对题目答案是 44 字节说明这个结构体里还有其他成员。看题目原文S6的定义应该是char c; short s; double d;再加上其他字段但输入里只给了部分信息。这里需要按题目给出的答案反推c在 0s在 36d在 40总大小 44。这说明结构体里还有别的成员占据了中间的空间。习题 28 是 Windows 平台的对齐规则要求不同基本类型按数据长度对齐。题目给出的结构体成员偏移是c在 0d在 8i在 16s在 20p在 24l在 28g在 32v在 40总大小 48 字节。调整成员顺序后按从大到小排列总大小可以降到 40 字节。这类题目的实际意义在于结构体对齐直接影响内存占用和访问效率。在嵌入式或性能敏感的场景里调整成员顺序减少填充字节是常见优化手段。但要注意不同平台的对齐规则不同跨平台代码里不能假设某个固定的对齐方式。4.2 数组寻址与比例变址习题 20 到习题 26 涉及数组的地址计算和寻址方式。习题 20 给出了char A[10]、int B[100]、short *C[5]等数组定义要求填写元素大小、数组大小、起始地址和元素地址表达式。这里的关键是区分数组元素类型和指针类型char A[10]的元素大小是 1 字节int B[100]的元素大小是 4 字节short *C[5]的元素是指针大小 4 字节。习题 21 给出了short S[]数组的多个表达式要求填写类型、值和对应的汇编代码。比如Si的类型是short *值是AS 2*i对应的汇编是leal (%edx, %ecx, 2), %eax。这里%edx存的是数组首地址AS%ecx存的是索引i比例因子 2 是因为short占 2 字节。习题 22 到习题 24 涉及多维数组的寻址。习题 22 的汇编代码里出现了M[a28*i4*j]和M[b20*j4*i]要求反推数组维度。因为int占 4 字节所以28*i说明每行有 7 个元素20*j说明每列有 5 个元素所以M5N7。习题 24 是矩阵转置的优化汇编代码要求还原 C 代码。汇编里用EDI存M19用ECX存j用指针p遍历列。还原出的 C 代码是void trans_matrix(int a[M][M]) { int i, j, t, *p; int c (M 2); for (i 0; i M; i) { p a[0][i]; for (j 0; j M; j) { t *p; *p a[i][j]; a[i][j] t; p c; } } }这段代码的优化点在于用指针p按列遍历避免每次计算a[j][i]的地址。实际写矩阵转置时这种指针遍历方式比双重循环下标访问效率更高因为减少了地址计算的开销。4.3 栈溢出与返回地址破坏习题 29 是一个典型的栈溢出分析题。题目给出了getline函数的汇编代码要求分析执行gets后栈中的内容变化以及为什么ret指令会导致段错误。关键信息是buf数组的首地址是R[ebp] - 0x14gets函数不检查输入长度如果输入超过 20 字节就会覆盖ebp的旧值、返回地址和被调用者保存寄存器。题目中执行gets后返回地址被覆盖为0x8413938这个地址不在可执行代码段所以ret时触发段错误。这道题的实际意义在于它展示了缓冲区溢出的基本原理。虽然现在编译器默认开启栈保护但理解栈帧布局和溢出后果对于写安全代码和排查内存错误仍然很重要。我自己的习惯是看到gets、strcpy、sprintf这类不检查长度的函数一律替换成带长度限制的版本。5. 从习题到实战用 GDB 验证答案和几个我踩过的坑5.1 用 GDB 单步验证寄存器变化答案里的寄存器值和标志位变化光看文字推导容易记混。我一般会写一个小汇编文件用gcc -m32 -g编译然后用 GDB 单步执行对照答案里的每一步。比如验证习题 8 第1题的add指令# 编译 32 位汇编 gcc -m32 -g -o test_add test_add.s # 启动 GDB gdb ./test_add # 在 _start 处下断点 (gdb) break _start (gdb) run # 单步执行查看寄存器和标志位 (gdb) si (gdb) info registers eax edx eflagseflags寄存器里的每一位对应 CF、PF、AF、ZF、SF、OF。GDB 会显示eflags的十六进制值需要手动展开。比如eflags 0x202表示 IF1其他标志位都是 0。如果执行add后eflags变成0x207说明 CF1、PF1、IF1。提示GDB 的info registers eflags不会直接显示每个标志位的名字需要自己对照 IA-32 手册展开。我一般会写一个小的 Python 脚本把eflags的值转成标志位列表省得每次手动算。5.2 结构体对齐的验证方法结构体对齐的题目答案里给的是偏移量和总大小但实际编译时可能因为编译器版本或编译选项不同而有差异。我一般用offsetof宏和sizeof来验证#include stdio.h #include stddef.h struct S1 { short s; char c; int i; double d; }; int main() { printf(sizeof(S1) %zu\n, sizeof(struct S1)); printf(offset of s %zu\n, offsetof(struct S1, s)); printf(offset of c %zu\n, offsetof(struct S1, c)); printf(offset of i %zu\n, offsetof(struct S1, i)); printf(offset of d %zu\n, offsetof(struct S1, d)); return 0; }编译时用-m32模拟 32 位环境用-fpack-struct或#pragma pack改变对齐规则观察输出变化。这样能直观看到对齐规则对内存布局的影响。注意不同编译器对double的对齐要求可能不同。Linux 下 GCC 默认按 4 字节对齐double但 Windows 下 MSVC 按 8 字节对齐。跨平台代码里如果依赖结构体内存布局一定要显式指定对齐方式。5.3 我踩过的几个坑第一个坑是混淆 ATT 和 Intel 汇编语法。答案里用的是 ATT 格式源操作数在前、目的操作数在后立即数加$寄存器加%。如果习惯 Intel 格式看答案时容易把源和目的搞反。我的做法是看答案时先在纸上把每条指令转成 Intel 格式再对照寄存器变化。第二个坑是忽略指令长度对条件转移的影响。习题 11 里计算目标地址时必须把指令本身的长度算进去。我一开始只加了偏移量忘了加指令长度结果地址差了 2 字节。后来养成习惯算目标地址前先数一下指令占几个字节。第三个坑是结构体对齐里double的对齐规则。IA-32 Linux 下double按 4 字节对齐但 x86-64 下按 8 字节对齐。如果题目没明确说平台需要根据上下文判断。习题 27 里明确说了“Linux 下 double 型按 4 字节对齐”这种信息不能漏看。第四个坑是栈帧分析时搞错ebp和esp的变化时机。push指令先减esp再写内存pop指令先读内存再加esp。习题 18 里追踪pushl、leave、ret对esp的影响时必须严格按这个顺序算。我一开始把push的顺序搞反了导致esp的值差了 4 字节。第五个坑是test指令和and指令的区别。test不写回结果只影响标志位and会写回结果。习题 8 第4题里test指令执行后dl寄存器的值不变但标志位变了。如果误以为test会修改寄存器后面的推导就全错了。从那以后我每次做这类题目都会先把指令的语义、操作数长度、标志位影响列成一张小表再逐条推演。希望这些经验能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑