4.3.5【2017统考真题】


好的,这是一道非常深刻的计算机组成原理综合题,它将C语言的高级代码、汇编级的指令表示以及底层的硬件标志位行为联系在了一起,全面考察了从软件到硬件的贯穿理解能力。
我们来详细地解析这道题。
首先,附上题目原文:
题目原文
4.3.5 程序的机器级代码表示
(1)【2017统考真题】在按字节编址的计算机M上, f1部分源程序(阴影部分)如下。将f1中的int都改成float,可得到计算f(n)的另一个函数f2。
// f1 function
int f1(unsigned n){int sum = 1, power = 1;for(unsigned i=0; i<n-1; i++){ //阴影部分C代码power *= 2;sum += power;}return sum;
}
对应的机器级代码(包括指令的虚拟地址)如下:
// Machine code snippet for f1
1 00401020 55 push ebp...
20 0040105E 39 4D F4 cmp dword ptr [ebp-0CH],ecx...
23 00401066 D1 E2 shl edx,1...
35 0040107F C3 ret
其中,机器级代码行包括行号、虚拟地址、机器指令和汇编指令。
- 计算机M是RISC还是CISC?为什么?
- f1的机器指令代码共占多少字节?要求给出计算过程。
- 第20条指令
cmp通过i减n-1实现对i和n-1的比较。在执行f1(0)的过程中,当i=0时,cmp指令执行后,进位/借位标志CF的内容是什么?要求给出计算过程。 - (隐含问题)第23条指令
shl通过左移操作实现了power*2运算,那么在f2中能否用shl指令实现power*2?为什么?
综合解析
这道题的核心是**“代码的表里不一”**,即同样一行C代码,在不同的数据类型和上下文下,其底层的机器实现和行为是完全不同的。
一、运用了什么知识点?
- 指令集架构 (ISA) 分类:
- CISC (复杂指令集): 指令长度可变,指令功能复杂(如一条指令可完成内存读+运算),寻址方式多样。
- RISC (精简指令集): 指令长度固定,指令功能简单,采用Load-Store架构(只有load/store指令能访问内存)。
- 程序地址与大小计算: 理解程序的指令在内存中是连续存放的,可以通过起始地址和结束地址计算其大小。
- 无符号整数运算: 理解
unsigned类型在发生下溢(如0U - 1U)时的回绕 (Wrap-around) 行为。 - CPU标志寄存器:
cmp指令的本质是做减法并根据结果设置标志位。- CF (Carry Flag, 进位/借位标志): 在执行减法
A - B时,如果A < B(作为无符号数),需要向高位借位,此时CF=1。
- 数据表示: 深刻理解**整型(int)和浮点型(float)**在计算机内部的二进制表示是完全不同的。
float采用IEEE 754标准(符号-阶码-尾数),而int采用补码。
二、考了什么?为什么这么考?
- 第1问:考察对计算机体系结构分类的理解。要求考生能从最直观的特征(指令长度、指令复杂度)来判断一个CPU的“血统”。
- 第 2问:考察基本的地址计算能力,这是阅读和理解汇编代码的基础。
- 第3问:这是本题的第一个难点和陷阱。它将C语言的
unsigned类型、for循环的边界条件和底层硬件的cmp指令、CF标志位联系起来,全面考察考生是否理解无符号数运算的特殊性及其对程序控制流的真实影响。 - 第4问:这是本题的第二个难点。它考察考生是否理解,同样是
*2这个运算,对于int和float两种类型,其硬件实现是天壤之别的。这深刻揭示了数据类型对底层指令选择的决定性作用。
为什么这么考? 因为这道题完美地展示了“编译”这个过程的奇妙之处。它将程序员视角的高级语言,翻译成了机器视角的二进制指令,而这个翻译过程充满了各种由硬件特性决定的“规则”和“陷阱”。能做对这道题,说明考生具备了从软件源码一直“看穿”到硬件执行细节的强大能力。
三、解题思路与详细分析 (为什么怎么样?)
问题1分析:RISC vs. CISC
- 核心思路: 寻找CISC的典型特征。
- 证据1:指令长度可变。
- 从汇编代码中可以看到:
push ebp(55) 占1字节;shl edx,1(D1 E2) 占2字节;cmp ...(39 4D F4) 占3字节。 - 指令长度不固定,这是CISC的显著特征(RISC指令通常是定长的)。
- 从汇编代码中可以看到:
- 证据2:复杂的内存操作指令。
cmp dword ptr [ebp-0CH],ecx这条指令,在一条指令内完成了内存寻址([ebp-0CH],从内存中取数)和比较两个复杂操作。- 这是CISC的特征。RISC采用Load-Store架构,必须先用一条
load指令将内存中的数加载到寄存器,再用一条cmp指令对两个寄存器进行比较。
- 结论: 计算机M是 CISC。
问题2分析:指令代码大小
- 思路: 代码总字节数 = 最后一个字节的地址 - 第一个字节的地址 + 1。
- 计算:
- 第一条指令
push ebp的地址是00401020H。 - 最后一条指令
ret的地址是0040107FH。 ret指令(C3)本身占1个字节。总字节数 = 0040107FH - 00401020H + 1 = 5FH + 1 = 60H。- 将十六进制
60H转换为十进制:6 * 16¹ = 96。
- 第一条指令
- 结论: f1的机器指令代码共占 96 字节。
问题3分析:cmp指令与CF标志位
- 分析场景: 执行
f1(0),此时n=0。循环刚开始,i=0。 - 分析比较对象:
cmp指令实现i和n-1的比较,实际上是执行i - (n-1)的减法运算。 - 关键陷阱:
unsigned运算!n和i都是unsigned int类型。- 计算
n-1时,执行的是(unsigned)0 - (unsigned)1。 - 无符号整数会发生下溢回绕。在32位系统中,结果是
2³² - 1,即十六进制的FFFFFFFFH。
- 执行减法:
cmp指令执行的是(unsigned)0 - (unsigned)FFFFFFFFH。
- 判断CF位:
CF位在减法中作为借位标志。- 当执行
A - B时,如果无符号数A < B,就需要向高位借位,此时CF置1。 - 在这个场景中,
A=0,B=FFFFFFFFH。显然A < B。 - 因此,运算需要借位。
- 结论:
cmp指令执行后,CF的内容是1。- 计算过程: 因为
n为unsigned类型,当n=0时,n-1的机器数为FFFFFFFFH。cmp指令执行i-(n-1),即0 - FFFFFFFFH。因为被减数小于减数,产生借位,所以CF=1。
- 计算过程: 因为
问题4分析:shl指令用于float类型
- 思路:
power*2对于int和float,在底层是完全不同的操作。 shl指令的作用:shl(Shift Logical Left) 是一条位运算指令。它将一个寄存器中的所有二进制位作为一个整体向左移动,低位补0。对于无符号整数,左移一位等效于乘以2。float的内部表示:float类型采用IEEE 754标准,其32位被划分为三部分:[符号位(1)] [阶码(8)] [尾数(23)]。它的值是通过(-1)^S * 1.M * 2^(E-127)这样的公式计算出来的。float乘以2的正确操作:- 要将一个
float数乘以2,在大多数情况下,只需要将其阶码(E)字段加1即可。 - 这需要专门的**浮点运算单元(FPU)**中的指令来完成,而不是通用的整数ALU中的
shl指令。
- 要将一个
- 为什么不能用
shl:- 如果对一个
float数的32位二进制表示执行shl,会同时移动符号位、阶码和尾数,这将彻底破坏其数值结构,得到一个毫无意义的结果。例如,可能会把阶码的高位移到符号位,把尾数的高位移到阶码,等等。
- 如果对一个
- 结论: 不能。因为
shl是针对整数的位操作指令,而float有其特定的符号-阶码-尾数表示格式。对float的二进制表示进行整体左移会破坏其结构,无法实现乘以2的运算。float的乘法需要由浮点运算单元执行专门的浮点乘法指令。
