CMU-CSAPP-datalab做后总结
datalab实验记录
本博 主要参考一位[知乎博主](https://zhuanlan.zhihu.com/p/59534845),第一次实验,特此记录一下。
本次实验记录是基于 CSAPP 3.0,实验日期始于:2019-9-20
实验所需压缩包请访问 CSAPP_LAP
该实验需要在Linux环境下进行操作,所以大家可以安装VMware来创建Linux环境,我自己用的Ubuntu镜像,比较好用。
另外,有些人一开始打开压缩包是懵逼的,因为有好多文件,其实里面可用的就几个bits.c进行实验(即用c编写代码就在这个地方)每次修改完之后要记得保存
driver.pl 在终端输入 ./driver.pl 的命令即可得到运行后的得分情况,有的人是运行btest.c文件,但我没用实现。相比较于运行btest.c,上述方法相较简单一些
实验打算以下的框架来记录
题目列表
loop //详细解释
{
题解
思考
代码
}
结果
关于本次实验的思考
题目列表
题解
bitXor(x,y)
- bitXor - x^y using only ~ and &
- Example: bitXor(4, 5) = 1
- Legal ops: ~ &
- Max ops: 14
- Rating: 1
用 ~ 和 & 两个运算符来实现异或操作,即x^y。
思考
这题比较简单,有关数理逻辑的内容。
异或,即当 a 与 b 不相同时为真,相同时为假,即 a^b=(¬a ∧ b) ∨ (a ∧¬b) ,因为只提供 ~ 和 & 两个运算符来实现异或操作,所以需要将 ∨ 改成 ∧ ,这里用到了分配律+摩根定律,不熟悉的可以网上查找。
这里直接给出结果 :
- a^b=(¬a ∧ b) ∨(a ∧¬b)= (¬a∨a)∧ (¬a∨¬b)∧ (b∨a) ∧ (¬b∨b)
= (¬a∨¬b)∧ (b∨a)=¬ (a∨b) ∧ (¬ (¬ b∨¬ a))
代码
int bitXor(int x, int y) {
return ( ~(x&y) ) & ( ~( (~x)&(~y) ) );
}
__________________________________________________________________
tmin()
- tmin - return minimum two’s complement integer
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 4
- Rating: 1
返回最小的二进制补码
思考
这题送分题,只需将 1 移到最高位即可。
代码
int tmin(void) {
return 1<<31;
}
____________________________________________________________________
isTmax(x)
- isTmax - returns 1 if x is the maximum, two’s complement number,
- and 0 otherwise
- Legal ops: ! ~ & ^ | +
- Max ops: 10
- Rating: 1
判定值 x 是否为补码的最大值 Tmax,是则返回1,反之为0。
思考
这一题我认为是 Rating:1 中最难的一题,甚至比有些 Rating:2还难。
首先要先明白
- Tmax=0111 1111 1111 1111 1111 1111 1111 1111
要想使返回值为1,假使这时 x=Tmax (我认为这是一种很重要的思想,将 x假设为你想要返回为真的那个值,然后一步步往下去想) 则要将 x 往0上去想,也就是 x 怎么才能变成0,为0后再取非 !,则得到结果 1 。
这里提出一种方法,当 x =Tmax 时,则 x+x+1+1=0 (x转换为了0),但同时也要注意到当 x=0xFFFFFFFF 即全1时,上述方法同样成立,因此为了得到返回值1,就需要排除x=0xFFFFFFFF (实质就是寻找一种方法,怎样才能让 0xFFFFFFFF 转换为非0的同时,Tmax 能够转换为0,那样再与高亮的式子相加取反,便能得到结果) 。
- 因为
temp=0xFFFFFFFF ~temp=0 !( ~temp)=1
temp=Tmax ~temp=Tmin !( ~temp)=0
则 ! (x+x+1+1+!(~x)) 即可得到结果
代码
int isTmax(int x) {
return !(x+x+1+1+!(~x));
}
____________________________________________________________________
allOddbits(x)
- allOddBits - return 1 if all odd-numbered bits in word set to 1
- where bits are numbered from 0 (least significant) to 31 (most significant)
- Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 12
- Rating: 2
返回1,如果所有的奇数位上的数字均为1。
思考
最直接的想法是将 x 与 0xAAAAAAAA进行 & 运算,得到 x & 0xAAAAAAAA ,即将 x 所有奇数位的位值分离出来,此时偶数位位值均为 0 。
为了进一步确认奇数位上所有的位值是否全为 1 ,再将 x 与 0xAAAAAAAA进行 ^ 运算,返回 0 ,再取非 ! ,即可得到真值 1。
那么现在的问题就是怎么去构造一个0xAAAAAAAA ,因为程序所允许输入的范围只是 0~0xff 。
这里提出一种方法:先取初值 0xAA ,然后对其不断移位累加求和,即可得到 0xAAAAAAAA,具体实现参见代码。
代码
int allOddBits(int x) {
int temp=0xaa+(0xaa<<8);//为了构造出0xAAAAAAAA
temp=temp+(temp<<16);
x=temp&x;//偶数位全为零,只保留奇数位上的数值
return !(temp^x);
//return !((temp&x)^temp);
}
____________________________________________________________________
nagate(x)
- negate - return -x
- Example: negate(1) = -1.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 5
- Rating: 2
这一题也很简单,怎么取负数,送分题
思考
方法就是直接取反+1
代码
int negate(int x) {
return ~x+1;
}
____________________________________________________________________
isAsciiDigit(x)
- isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters ‘0’ to ‘9’)
- Example:
isAsciiDigit(0x35) = 1.
isAsciiDigit(0x3a) = 0.
isAsciiDigit(0x05) = 0. - Legal ops: ! ~ & ^ | + << >>
- Max ops: 15
- Rating: 3
也就是判断 x 是否在 [0x30,0x39] 内。
思考
很容易可以发现:
0x30 0x39
—————————————————————————————————————
符号 符号 符号
x-0x30<0 1 x-0x30>=0 0 x-0x30>0 0
x-0x39<0 1 x-0x39<=0 0 or 1 x-0x39>0 0
可以发现当我们试图通过 减去0x30 0x39来判定范围时,符号位可能出现两种情况,这样就给判断增加了难度(因为我们只想出现唯一的一种情况,不想进行选择判断), 我一开始思考的时候就一直陷入了这个误区,之后才发现这个问题。
于是我们不去减0x39 ,而是去减0x3a,这样在区间内的判断就变得唯一了。
0x30 0x3a —————————————————————————————————————
符号 符号 符号
x-0x30< 0 1 x-0x30>=0 0 x-0x30>0 0
x-0x3a< 0 1 x-0x3a< 0 1 x-0x3a>=0 0
然后对x-0x30的值进行取反再取符号位,将两式的符号位进行 & 操作,即可得到结果。
代码
int isAsciiDigit(int x) {
int low=(~(x+(~0x30+1))>>31)&0x1;//x-0x30>=0 并取其符号位
int high=((x+(~0x3a+1))>>31)&0x1;//x-0x3a<0 并取其符号位
return low&high;
}
____________________________________________________________________
conditional(x, y, z)
- conditional - same as x ? y : z
- Example: conditional(2,4,5) = 4
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 16
- Rating: 3
进行选择输出,x=0时为假,返回z的值,反之为真,返回y的值。实质就是c语言中 x ?y:z的实现。
思考
首先,要明白的是,x等于多少是不重要的,它无非会有两种结果,0 或 非0 ,所以第一步就要将0取非,来得到一位值1 或 0, 即 x=!x。
这一题实质上是让我们用他所给的操作符来实现一个选择判断,我刚开始想的是,是否能用这八个操作符来实现一个类似于 if…else的结构,但发现很难实现。
后面就考虑了以下的做法:能够输出一个值的同时,势必能让另一个值为零。也就是在y和z前面加上一个系数 (这种说法不是很准确,但相对容易明白),一个系数为全1的同时,另一个系数势必为全0。
可以想到 ~0=0xFFFFFFFF 可以实现这个操作,取反后即为0。但既然有x,势必是因为x的值 才决定说取y还是取z,因此,这里就要将x 与系数进行挂钩。上面已经得知x=!x后:
x !x x+0xFFFFFFFF
—————————————————————————————————————
0 1 0
非0 0 0xFFFFFFFF
那么假设x为非零时,我想输出 y ,则此时y的系数应该为0xFFFFFFFF,即可表示为 (x+(~0))&y,那么此时有关z的式子就为 ( ~ (x+(~0))&z 。
代码
int conditional(int x, int y, int z) {
x=!x;
return ( ( (~0+x) &y) + ( ( ~(~0+x) ) &z) );
}
____________________________________________________________________
isLessOrEqual(x,y)
- isLessOrEqual - if x <= y then return 1, else return 0
- Example: isLessOrEqual(4,5) = 1.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 24
- Rating: 3
判断两个数值的大小,如果x <= y 就返回1。
思考
对于这题,最直接的考虑肯定是y-x>=0,通过符号位进行判断即可。
但是,这么做是有问题的,我一开始就是这么考虑到,所以一直错,最后参考了别的博主的思考,才明白我少考虑了内容。
当x与y 同号时当然可以这么做,但当它俩异号时,这么做就会有溢出的风险。
因此,需要考虑两种情况:(signx signy分别为x y的符号位)
同号 :signx signy 执行操作:y-x>=0
0 0
1 1
异号:0 1 错误
1 0 正确
但我们发现同号时用操作符不是很方便得到1,因此这里做一个小小的改变,对y进行取反 (你会发现取反y,而不是取反x,会对下一步的操作造成影响)。那么符号位就会发生相应的变化,相当于signy全部取非了。
同号 :signx signy 执行操作:y-x>=0
0 1
1 0
异号:0 0 错误
1 1 正确
注意高亮部分,你就会发现为什么取反y,而不是取反x了。
该题实质上也是一个选择判断,因此我们也考虑同号和异号两种选择,一个系数为1的同时,另一个系数必然为0。可以发现,同号异号的符号位正好分为两种情况,那我们对signx和signy执行**^** 操作,即可得出同号为1,异号为0。
系数
同号操作: ( ( ~ (y+(~x+1) ) ) >>31 ) & 0x1 signx^signy
异号操作:signx & signy !(signx^signy)
再加上相应系数,即进行 & 操作。
代码
int isLessOrEqual(int x, int y) {
int signx=(x>>31)&0x1;//x的符号位
int signy=((~y)>>31)&0x1;//y先取反,再取其符号位
return ((signx^signy)&(((~(y+(~x+1)))>>31)&0x1))
+
(!(signx^signy)&(signx&signy));
}
____________________________________________________________________
logicalNeg(x)
- logicalNeg - implement the ! operator, using all of
the legal operators except ! - Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
- Legal ops: ~ & ^ | + << >>
- Max ops: 12
- Rating: 4
用其他七个操作符来实现 取非 的操作,不允许使用 !
思考
该题也进行了选择判断,一开始我也会是懵逼的,但因为有了前几题的经验,我就想x+(~0)试一试,看看会造成什么影响:
x x符号位 x+(~0)符号位 —————————————————————————————————————
0 0 1
|—— 正数 0 0
非0|
|—— 负数 1 1or0
惊喜出现了,我们发现只有高亮的那一组会出现 0 1 ,那么就考虑将x符号位取反为1,再将两个符号位进行**&**操作,不就可以返回真值了吗。
代码
int logicalNeg(int x) {
return ( ( (~x)>>31) &0x1 )&( ((~0+x)>>31) &0x1 );
}
____________________________________________________________________
howManyBits( x)
- howManyBits - return the minimum number of bits required to represent x in
two’s complement - Examples:
howManyBits(12) = 5
howManyBits(298) = 10
howManyBits(-5) = 4
howManyBits(0) = 1
howManyBits(-1) = 1
howManyBits(0x80000000) = 32 - Legal ops: ! ~ & ^ | + << >>
- Max ops: 90
- Rating: 4
求值:“一个数用补码表示最少需要几位?”
思考
这一题我认为是所有题目当中最难的一道,没有算法基础的很难想到,我没想出来,因此参考大佬的做法,写出我自己的理解。
首先要理解题目对位数的定义,负数可以就按正常补码的理解,而正数则要在正常的基础上加上一个符号位0,例如
howManyBits(12) = 5—>0 1 1 0 0,为五位,而非四位。
为了便于处理,我们将负数统一取反,之所以可以这么做是因为,为了确定负数用补码表示最少需要几位,则需要找到负数中从高位开始第一个为零的位,取反后则需要找到第一个为1 的位,实现的效果其实相一致 (实在不明白可以在纸上画一画)。
就像上面说的,我们需要找到第一个 1 的位置,得出从该位置算起有多少位,再加上一个符号位,就可以得出一个数用补码表示最少需要几位。
但难点在于我怎么去确定二进制数列中第一个1的位置。大佬写的是一种放缩的思维,首先我要假设一下一系列操作均成立:
假设1在高16位中,然后将x右移16位,则该补码至少需要16位。
假设1在高8位中,然后将x右移8位,则该补码至少需要16+8位。
假设1在高4位中,然后将x右移4位,则该补码至少需要16+8+4位。
假设1在高2位中,然后将x右移2位,则该补码至少需要16+8+4+2位。
假设1在高1位中,然后将x右移1位,则该补码至少需要16+8+4+2+1位。
假设1在高1位中,则x=1,则该补码至少需要16+8+4+2+1+1位。
最后不要忘记符号位,再加上1。
这应该是涉及到一些算法,不理解的可以查询相关算法。
代码
int howManyBits(int x) {
int b16,b8,b4,b2,b1,b0;
//int sign=x>>31;
//x = (sign&~x)|(~sign&x);
int sign=(x>>31)&0x1;
x=(((~0)+sign)&x)+((~((~0)+sign))&(~x));
b16=(!!(x>>16))<<4;
x=x>>b16;
b8=(!!(x>>8))<<3;
x=x>>b8;
b4=(!!(x>>4))<<2;
x=x>>b4;
b2=(!!(x>>2))<<1;
x=x>>b2;
b1=!!(x>>1);
x=x>>b1;
b0=x;
return b16+b8+b4+b2+b1+b0+1;
}
____________________________________________________________________
floatScale2( uf)
- floatScale2 - Return bit-level equivalent of expression 2*f for
floating point argument f. - Both the argument and result are passed as unsigned int’s, but
they are to be interpreted as the bit-level representation of
single-precision floating point values. - When argument is NaN, return argument
- Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
- Max ops: 30
- Rating: 4
求2乘以一个浮点数
思考
这一题实际上还比较好做。但你首先要明白的是,题目给你的是一个无符号整数,但你要将它看成一个补码,然后将这个补码乘以2后得到结果输出。对于NaN,则输出它本身。
sign exp frac
—————————————————————————————————————
N
a
N
0/1 111...111 000...000 INF
0/1 111...110 111...111 规格化最大值
. .
. .
0/1 000...001 000...000 规格化最小值
0/1 000...000 111...111 非规格化最大值
. .
. .
0/1 000...000 000...000 非规格化最小值0
可以很直观地看出要实现2×f,
1.当255>exp>0时,只需要exp+=0x800000,便可完成此操作。但不要忘了,随着exp的增加,exp可能会变为全1,这时,它就变成INF,则需要将frac赋值为0。
2.当exp=0时,则需要将frac左移一位。
我当初对exp=0的情况相当困惑,因为假如frac=0x7FFFFF时,即frac为全1时,frac左移后低位补零,那岂不是变小了。而实际情况则是frac左移后:frac=0xFFFFFE(即 1 111 1111 1111 1111 1111 1110),原来23位的frac变成了24位(即exp=0x800000了,计算方法也改变了)。
3.当exp=255时,则返回它本身。
代码
unsigned floatScale2(unsigned uf) {
unsigned sign=uf&0x80000000;
unsigned exp=uf&0x7f800000;
unsigned frac=uf&0x7fffff;
//if((!(exp^0x7f800000))==0) right
if(exp^0x7f800000)
{
if(!exp)
{
frac<<=1;
}
else
{
exp+=0x800000;
if((exp^0x7f800000)==0)
{
frac=0;
}
}
}
return sign|exp|frac;
}
____________________________________________________________________
floatFloat2Int( uf)
- floatFloat2Int - Return bit-level equivalent of expression (int) f
for floating point argument f. - Argument is passed as unsigned int, but
it is to be interpreted as the bit-level representation of a
single-precision floating point value. - Anything out of range (including NaN and infinity) should return
0x80000000u. - Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
- Max ops: 30
- Rating: 4
将浮点数转换为整数
思考
这一题也算比较绕的。这一题给出的仍是无符号int型,但我们要把他看成浮点型,容易搞混的话,最好的方法就是不管它是无符号 int型,直接将其看做浮点型来进行处理。我们先给出公式:M * (2^E)
sign exp frac
—————————————————————————————————————
N
a
N
0/1 111...111 000...000 INF
0/1 111...110 111...111 规格化最大值
. .
. .
0/1 000...001 000...000 规格化最小值
0/1 000...000 111...111 非规格化最大值
. .
. .
0/1 000...000 000...000 非规格化最小值0
一定要留意有正数负数两种情况,虽然大部分操作过程相一致。
通过简单的计算我们可以得知E=((uf&0x7f800000)>>23)-127,而且非规格化数值是一定小于0的(正数,自己可以乘乘看),也就是int型是无法表示的,太小了。所以能表示的范围只有规格化数字。那么M=frac_=frac+0x800000。
1.当E>30时,即使frac=1,符号位也会被覆盖,所以不可以,返回0x800000。
2.当E<0时,(1右移x位,x>0,结果为0)则返回0。
3.当0<=E<=30时,还需要进行两种考虑,即frac_左移右移的问题:
*当exp_>23时,则将frac_向左移动exp_-23位
*当exp_<23时,则将frac_向右移动23-exp_位
最后,首先把小数部分(23位)转化为整数(和23比较),然后判断是否溢出:如果和原符号相同则直接返回,否则如果结果为负(原来为正)则溢出返回越界指定值0x80000000u,否则原来为负,结果为正,则需要返回其补码(相反数)。
代码
int floatFloat2Int(unsigned uf) {
int s_ = uf>>31;
int exp_ = ((uf&0x7f800000)>>23)-127;
int frac_ = (uf&0x007fffff)|0x00800000;
if(!(uf&0x7fffffff)) return 0;
if(exp_ > 30) return 0x80000000;//当E>30时,即使frac=1,符号位也会被覆盖,所以不可以,返回0x800000。
if(exp_ < 0) return 0;//(1右移x位,x>0,结果为0)则返回0。
if(exp_ > 23) frac_ <<= (exp_-23);
else frac_ >>= (23-exp_);
if(!((frac_>>31)^s_)) return frac_;
else if(frac_>>31) return 0x80000000;
else return ~frac_+1;
}
____________________________________________________________________
floatPower2( x)
-
floatPower2 - Return bit-level equivalent of the expression 2.0^x
(2.0 raised to the power x) for any 32-bit integer x. -
The unsigned value that is returned should have the identical bit
representation as the single-precision floating-point number 2.0^x.
If the result is too small to be represented as a denorm, return
0. If too large, return +INF. -
Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while
-
Max ops: 30
-
Rating: 4
求 2.0^x
思考
这一题我没有特别理解,看不懂的再看看别人的。
首先得到偏移之后的指数值e(即为exp,这里要注意exp的范围是 (0,255) ),如果x+127还小于0,那它本身一定非常小,2.0^x也会很小。
1.如果e小于等于0(为0时,结果为0,因为2.0的浮点表示frac部分为0)。
2.如果e大于等于255则为无穷大或越界了。
3.否则返回正常浮点值,frac为0,直接对应指数即可。
代码
unsigned floatPower2(int x) {
int INF=0xff<<23;
int exp=x+127;
if(exp<=0) return 0;
if(exp>=255) return INF;
return exp<<23;
}
结果
最后一题参考大佬解法,他运行也出现了出错的问题,所以如果报错最后一题,那就多运行几次。
关于本次实验的总结
新晋研究生一枚,荒废了大学四年,好不容易考上研究生,真的打算洗心革面了。
这是我CSAPP实验的第一次实验,从2019-9-20左右开始,到2019-10-10完成,之所以拖了很久,是因为做了三四题的样子就突然卡克了,完全没有思路,本科玩了四年,懒散成性,就扔在一边好几天。不知道是不是良心突然发现,就又花了近一个星期耐心的做了下来。
但能力毕竟有限,四星题基本都是大佬的做法,我也仅能学习学习,尤其howManyBits( x)这题,没有一定的基础,真的是想破脑袋也做不出来。但看完之后,实际上float的几题难度都不是很大的,只要不进入一些误区,是能想出来的。
主要收获的是二星三星基本就都是自己独立完成的,刚开始真的认为自己做不出来,但当你一上午或者一下午坐在实验室就搞这些题时,真的慢慢搞出来时,对一个渣渣来说,那种成就感还是很强烈的。
所得
- 实验一定要自己做,即使完全没有思路,也不能完全照搬别人的代码,要先试着去看看别人的思路对你有什么启发,再自己去实现出来。
- 一定要坐得住,实际上大部分内容不是很难得,多想想总做得出来。
- 实验必须总结,定期回顾,更新思路。