前言
有幸作为助教参与信息院“周末夜校”讲解2023年试卷第2-3题(汇编),借这个契机重新温习了一下这份试卷。
以下各部分的PPT由讲评助教分别完成并讲解。
- 1,6题 @计科21杨助教
- 2,3题 @计科21甘晴void
- 4,5题 @智能21姚助教
由于2,3题过程较为繁杂而长,我将重新开一个页面专门讲解这两道题,这里只放答案。
> 甘晴void:由2023年CS两道汇编题看汇编题解法,争取满分-CSDN博客
4,5题感谢@22软件孙同学记下了答案
本张试卷答案经过核对无误,答案来源:2024春学期HNU-CS课程本科生助教。
【2024.6.18更新】第5题具体解释(题目确实存在歧义,能搞懂原理就可以了)
一.简答题(10 分)
小明仿照 IEEE 754
标准,对其进行了微调,形成了
HNU 2023
标准。
微调的地方为:
IEEE 754
标准中,对于
k
位阶码,其偏移值为
2^(
k-1)
-1
,而
HNU 2023
标准中,规定偏移值为 2^
k
-1
。
以
10bit
数字(其中
1bit
为符号位,
4bit
为阶码位,
5bit
为尾数位)为例,仅讨论
正数的情况。
请回答:
(
1
)
HNU 2023
标准与
IEEE754
标准相比,其表示的数字范围有何区别?
(
2
)对于
HNU 2023
标准与
IEEE754
标准,其不能表示的最小整数分别是多少?请写出其十进制值与二进制值表示。
【解答】(PPT@计科21杨助教):
(1)
(2)该处的思路应该没问题,但答案有点问题,要自己看一下。
二.程序填空题(10 分,每空 2 分)
如下是一个
c
语言程序及其对应的汇编代码(
32
位机,小端环境下编译),请参照汇编代码,完成 c
程序的空缺部分。
c
语言程序:
int main()
{
int i,j,flag;
int sum= (1)
for( (2) ; (3) ;i++,j+=2)
{
flag= (4) ;
sum+= (5) ;
}
return sum;
}
汇编代码如下:
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $17, -8(%ebp)
movl $5, -16(%ebp)
movl $1, -12(%ebp)
jmp .L2
.L5:
movl -16(%ebp), %eax
andl $1, %eax
testl %eax, %eax
jne .L3
movl -12(%ebp), %eax
addl $3, %eax
jmp .L4
.L3:
movl -16(%ebp), %eax
subl $2, %eax
.L4:
movl %eax, -4(%ebp)
subl $1, -16(%ebp)
movl -12(%ebp), %eax
imull -16(%ebp), %eax
addl -4(%ebp), %eax
addl %eax, -8(%ebp)
addl $1, -12(%ebp)
addl $1, -16(%ebp)
addl $2, -12(%ebp)
.L2:
movl -12(%ebp), %eax
movl -16(%ebp), %edx
addl %edx, %eax
cmpl $98, %eax
jle .L5
movl -8(%ebp), %eax
leave
ret
【答案】
三.程序填空题(第一空 3 分,后面三空每空 4 分,共 15 分)
如下是一个
c
语言程序及其对应的汇编代码(
32
位机,小端环境下编译),请参照汇编代码, 完成 c
程序的空缺部分。
c
语言程序:
int f(int a,int b)
{
if(0==a) return (1) ;
return (2) ;
}
int g(int x)
{
if(x<=0) return 0;
if(x==1||x==2) return 1;
return (3) ;
}
int main()
{
int x=3,y=5;
return f( (4) );
return 0;
}
汇编代码如下:
f:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
cmpl $0, 8(%ebp)
jne .L2
movl 12(%ebp), %eax
imull 12(%ebp), %eax
jmp .L3
.L2:
movl 12(%ebp), %eax
movl 8(%ebp), %edx
addl %edx, %eax
movl %eax, (%esp)
call g
.L3:
leave
ret
g:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $20, %esp
cmpl $0, 8(%ebp)
jg .L5
movl $0, %eax
jmp .L6
.L5:
cmpl $1, 8(%ebp)
je .L7
cmpl $2, 8(%ebp)
jne .L8
.L7:
movl $1, %eax
jmp .L6
.L8:
movl 8(%ebp), %eax
subl $2, %eax
movl %eax, (%esp)
call g
leal (%eax,%eax), %ebx
movl 8(%ebp), %eax
addl $3, %eax
movl %eax, (%esp)
call g
movl %eax, %edx
movl %edx, %eax
addl %eax, %eax
addl %edx, %eax
addl %ebx, %eax
.L6:
addl $20, %esp
popl %ebx
popl %ebp
ret
main:
pushl %ebp
movl %esp, %ebp
pushl %ebx
andl $-16, %esp
subl $32, %esp
movl $3, 24(%esp)
movl $5, 28(%esp)
movl 28(%esp), %eax
movl 24(%esp), %edx
movl %edx, %ecx
subl %eax, %ecx
movl %ecx, %eax
movl %eax, (%esp)
call g
movl %eax, %ebx
movl 28(%esp), %eax
movl %eax, 4(%esp)
movl 24(%esp), %eax
movl %eax, (%esp)
call f
imull %ebx, %eax
movl 28(%esp), %edx
movl 24(%esp), %ecx
addl %ecx, %edx
movl %eax, 4(%esp)
movl %edx, (%esp)
call f
movl -4(%ebp), %ebx
leave
ret
【答案】
四.综合应用题(20 分)
一个函数调用的
C
代码如下:
#include "stdio.h"
void test(int *xp,int *yp,int i,int j)
{
int size=sizeof(int), step=(i*(3*size)+j*size)/4;
int *addr1=(int*)(xp+step);
int *addr2=(int*)(yp+step);
int m=*addr1, n=*addr2;
*addr1=n;
*addr2=m;
}
int main()
{
int A[3][3]={{1,4,7},{2,5,8},{3,6,9}};
int B[3][3]={{10,20,30},{40,50,60},{70,80,90}};
int *PA=*A;
int *PB=*B;
test(PA,PB,1,2);
printf("A[1][2]=%d\n B[1][2]=%d\n", A[1][2],B[1][2]);
return 0;
}
1.
该程序运行后,打印在屏幕上的结果是
_______________________(5
分
)
;
2.
将返回地址“
Rtn Addr”
,填入栈帧图中准确位置(
3
分);
3.
在主函数栈帧中的正确位置,填写
4
个传递参数(
12
分);
*
说明:当前
ESP
与
EBP
指向子函数栈帧,图中每一格为
4
字节。
![](https://img-blog.csdnimg.cn/direct/0bb5a3d03c7f43e19b22f19eb9241a84.png)
【答案】
(1) 60 8
(2)(3)如图所示
![](https://img-blog.csdnimg.cn/direct/bee37cbae511458dae34a082a455e300.png)
评价:这题难度还是比较小的,但是考察了C语言int型指针的大小,这个如果不知道,容易暴毙
五.综合应用题(20 分)
给定一个
32
位
Linux
系统,其高速缓存的大小为
64
字节,
2
路组相联,每个块
16
字节。 高速缓存采用的写策略是直写,替换策略为 LRU(
最近最少使用
)
。
A
)(
2
分)
基础知识
A1)
高速缓存中有多少个组
?
A2)
每个组中有多少缓存行
?
B
)
(10
分
)
假设如下
:
- 访问一次内存消耗 100 ns
- 访问一次 cache 的开销是 1 ns
- 忽略可能发生的其他时间开销(回收、存储到高速缓存等)
- 如果使用高速缓存,我们总是会先访问高速缓存(因此,在这种情况下,高速缓存未命中的开销为 101 ns)
给定一个整数数组
:
int Arr[6][4];
Arr
数组从地址
0x00000000
开始。
如果我们要逐一访问数组中的以下元素
:
B1)
用
“H”
或
“M”
填空,分别表示高速缓存命中和高速缓存未命中。
![](https://img-blog.csdnimg.cn/direct/731bec4f642548af86cfcc0ab6854c61.png)
B2
)如果不使用高速缓存,时间开销是多少
?
B3
)如果使用高速缓存,时间开销是多少
?
C)
(
8
分)
如果我们使用以下程序访问数组
:
int i, j;
for ( i = 0; i < 4; i++) {
for ( j = 0; j < 6; j++) {
Arr[j][i]++;
}
}
C1)
如果不使用高速缓存,时间开销是多少
?
C2)
如果使用高速缓存,时间开销是多少
?
【答案】
(A.1)2
(A.2)2
(B.1)从上至下依次: M H H M H M
(B.2)600ns 306ns
(C.1)4800ns
(C.1)4800ns
(C.2)2448ns
【解答过程】
(A)
该部分显然可计算得2组,每组2行
(B.1)
由该缓存1个块有16字节,每个int型是4个字节,可知1个块可以存下数组中的连续4个。并且数组是从0x0开始的地址,说明是对齐的。比较巧合的是这里每个块正好就是对应数组中的一行(如A[0][0]、A[0][1]、A[0][2]、A[0][3]这样是一行,我们称这个为第0行,以此类推,同一时间里,cache最多能容纳4个这样的行)。
由这样可知,第一个
A[0][0]
是冷不命中,此时会把第0行都放进来。
之后两次
A[0][2]和A[0][3]是命中的。
再之后A[1][1]又是冷不命中,此时把第1行都放进来。
A[1][3]命中。
再之后A[2][1]又是冷不命中,此时把第2行都放进来。
结束。
(B.2)
不使用cache,相当于每次都不命中,访问6次内存,6 * 100ns = 600ns
(B.3)
使用cache,
不命中时:未命中开销101ns(题目中明确提到忽略更新cache的开销)
命中时:1ns
根据(B.1)有101 * 3 + 1 * 3 = 306 ns
(C.1)
C题一个注意点是这个“++”,写完整是Arr[j][i] = Arr[j][i] + 1;
它有两次访存的过程。(这一点21级基本全军覆没,没考虑到)
不使用cache,相当于每次都不命中,访问24*2次内存,48 * 100ns = 4800ns
(C.2)
使用cache,由于使用LRU方式进行cache替换,从行的观点来看,会先移入0,1,2,3行,此时4行将覆盖0行,5行将覆盖1行。之后又要访问0行,将覆盖2行,……以此类推。
可以发现很巧合的是,正好每行的访问都是不命中的。
但是C题对于每行,实际上是紧挨着访问两次的,对于每个Arr[j][i] = Arr[j][i] + 1;来说,第一次访问Arr[j][i]都是不命中的,但第二次访问Arr[j][i]是命中的。
所以列式如下: (101 + 1)* 24 = 2448ns
【评述】
本题主要最后C题的“++”需要访问两次没有考虑到,这一点确实比较难想。
题目确实存在一点歧义,能搞懂原因就可以了,如果考试遇到可以询问老师。
六.综合分析题(25 分)
现有如下
C
代码片断:
……
extern int number=2
int sharedV(){ return number };
int main ()
{
int i,child_status;
pid_t pid[sharedV];
void myHandler(int sig) {
printf("Process received signal\n");
exit(0); }
signal(SIGINT, myHandler);
for(i=0;i< sharedV(); i++) {
if ((pid[i]=fork())==0) { while(1);} }
for (i=0; i<sharedV(); i++) {
kill(pid[i],SIGINT);}
for (i=0;i<sharedV(); i++) {
pid_t wpid=wait(&child_status);
if (WIFEXITED(child_status))
printf("Process %d: Hello,World!%d\n",wpid,WEXITSTATUS(child_status));
}
请阅读分析上述
C
代码后,尝试解答下列问题:
(
1
)这段代码的输出结果可能是什么?为什么会有这样的输出?
(
2
)在这段代码中
wait
函数的作用是什么?如果改用
waitpid
函数可能会产生什么影响?
(
3
)当观察该代码对应的可重定位目标文件时,发现如下信息:
![](https://img-blog.csdnimg.cn/direct/32c51622e80a47479e939e63c60df9ab.png)
请问这两行是什么意思?作用是什么?在可执行文件里面这两行会产生什么变化?
(
4
)对可执行文件进行反汇编观察时,发现在
<main>
部分对应
printf
函数调用的地方出现了如下汇编代码:
call 80483e0 <printf@plt>
继而查看
0X80483e0
地址处的内容,发现出现如下语句:
jmp *0x804a00c
push $0x0
jmp 80483d0 <_init+0x24>
请结合你学习的
PIC
的相关知识尝试解答这几条语句是干什么用的,以及后续对printf 的重定位过程。
(
5
)对于题干中的代码,不考虑数据结构和算法的变更,做什么修订可能能改善该代码的执行性能?
【解答】(PPT
@计科21杨助教):
(1)
![](https://img-blog.csdnimg.cn/direct/07848a6af961496d909b16739b500477.png)
(2)
![](https://img-blog.csdnimg.cn/direct/d272d0687790424899d8e58772ce2ed1.png)
(3)
![](https://img-blog.csdnimg.cn/direct/b598421834e148aeb67b9566b1b7533c.png)
(4)
![](https://img-blog.csdnimg.cn/direct/0d94cc5a21c747179fdd74a8706ba042.png)
(5)
![](https://img-blog.csdnimg.cn/direct/5c477fd59b2047309364327074a961f4.png)