第45题
已知f(n)=n!=n×(n-1)×(n-2)×···×2×1,计算 f(n)的 C 语言函数 f1 的源程 序(阴影部分)及其在 32 位计算机 M 上的部分机器级代码如下:
int f1(int n){
1 00401000 55 push ebp
… … …
if(n>1)
1100401018 83 7D 08 01 cmp dword ptr [ebp+8],1
120040101C 7E 17 jle f1+35h (00401035)
return n*f1(n-1);
130040101E 8B 45 08 mov eax, dword ptr [ebp+8]
1400401021 83 E8 01 sub eax, 1
1500401024 50 push eax
1600401025 E8 D6 FF FF FF call f1 ( 00401000)
… … …
1900401030 0F AF C1 imul eax, ecx
2000401033 EB 05 jmp f1+3Ah (0040103a)
else return 1;
2100401035 B8 01 00 00 00 mov eax,1
}
… … …
2600401040 3B EC cmp ebp, esp
… … …
300040104A C3 ret
其中,机器级代码行包括行号、虚拟地址、机器指令和汇编指令,计算机 M 按字节编址,int 型数据占 32 位。请回答下列问题:
(1)计算 f(10)需要调用函数 f1 多少次?执行哪条指令会递归调用 f1?
(2)上述代码中,哪条指令是条件转移指令?哪几条指令一定会使程序跳转执行?
(3)根据第 16 行的 call 指令,第 17 行指令的虚拟地址应是多少?已知第 16 行的 call 指令 题 47 图 采用相对寻址方式,该指令中的偏移量应是多少(给出计算过程)?已知第 16 行的 call 指令的 后 4 字节为偏移量,M 是采用大端方式还是采用小端方式?
(4)f(13) = 6227020800,但 f1(13)的返回值为 1932053504,为什么两者不相等?要使 f1(13)能返回正确的结果,应如何修改 f1 的源程序?
(5)第 19 行的 imul 指令(带符号整数乘)的功能是 R[eax]←R[eax]×R[ecx],当乘法器输 出的高、低 32 位乘积之间满足什么条件时,溢出标志 OF = 1?要使 CPU 在发生溢出时转异常 处理,编译器应在 imul 指令后应加一条什么指令?
【答案解析】
(1)计算 f(10)需要调用函数 f1 共 10 次,执行第 16 行的 call 指令会递归调用 f1。
(2)第 12 行的 jle 指令是条件转移指令,其含义为小于等于时转移,本行代码的意义为:当 n≤1 时,跳转至地址 0040 1035H。第 16 行的 call 指令为函数调用指令,第 20 行的 jmp 指 令为无条件转移指令,第 30 行的 ret 指令为子程序的返回指令,这三条指令一定会使程序跳 转执行。
(3)其长度计算机 M 上按字节编址,第 16 行的 call 指令的虚拟地址为 0040 1025H,长度 为 5 字节,故第 17 行的指令的虚拟地址为 0040 1025H + 5 = 0040 102AH 。第 16 行的 call 指令采用相对寻址方式,即目标地址= (PC) +偏移量,call 指令的目标地址为 0040 1000H, 所以偏移量=目标地址- (PC) = 0040 1000H - 0040 102AH = FFFF FFD6H。根据第 16 行的 call 指令的偏移量字段为 D6 FF FF FF,可以确定 M 采用小端方式。
(4)因为 f(13) = 6227020800,其结果超出了 32 位 int 型数据可表示的最大范围,因此 f(13) 的返回值是一个发生了溢出的错误结果。为使 f1(13)能返回正确结果,可将函数 f1 的返回值 类型改为 double(或 long long,或 long double,或 float)类型。
(5)若乘积的高 33 位为非全 0 或非全 1,则 OF=1。编译器应在 imul 指令后加一条“溢出 自陷指令”,使得 CPU 自动查询溢出标志 OF,当 OF=1 时调出“溢出异常处理程序”。