CS:APP 实验DataLab

  • Post author:
  • Post category:其他




实验及其环境


Data Lab


[Updated 12/16/19]


环境: Ubantu

gcc版本: 4.8.5



常用命令

make:编译所有文件

./dlc bits.c: 测试使用的操作符是否合法

./btest -h //测试命令的功能列表,按照说明使用即可

如./btest -f bitXor,测试bitXor是否能通过所有的测试用例

在data-handout的readme文件中对上述命令有具体说明,在此不多赘述



题解



第一关



1.bitXor

/* 
 * bitXor - x^y using only ~ and & 
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14
 *   Rating: 1
 */

大意:使用~和&来完成^运算符。

大致思路是先完成

( ~x & y) | ( x & ~y)

运算,最后利用摩根定律,使用~和&运算符来替代

|

操作符。

int bitXor(int x, int y) {
  return ~(~(~x & y) & ~( x & ~y));
}



2.tmin

/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */

大意:求Tmin

送分题,直接移位

int tmin(void) {

  return 1 << 31;

}



3.isTmax

/*
 * isTmax - returns 1 if x is the maximum, two's complement number,
 *     and 0 otherwise 
 *   Legal ops: ! ~ & ^ | +
 *   Max ops: 10
 *   Rating: 1
 */

大意:判断给出的x是否是Tmax。

如果x == Tmax,则有

!~((x + 1) ^ x ) = 1

,当然 x == -1时,该等式也成立。因此特判一下 x != -1,此时

!!(x + 1)

应该为0x1,等式成立只有这两种情况了。

int isTmax(int x) {
  return !~((x + 1) ^ x ) &!!(x + 1);
}



第二关



1.allOddBits

/* 
 * 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
 */

大意:如果x中所有的奇数位都为1则返回1,反之返回0.(x位数范围为0-31),偶数位可以不用管。

取其中的一个字节10101010,十进制为170,将其生成为一个所有奇数位都为1的数字mask(10101010 10101010 10101010 10101010),拿这个数字和x来比较,如果x == mask,则必有

(x&mask) ^ mask == 0

(a^a == 0, x&mask是为了去掉偶数位)

int allOddBits(int x) {
  int mask = 170 + (170 << 8) + (170 << 16) + (170 << 24);
  return  !((x & mask) ^ mask);
}



2.negate

/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */

大意:求相反数

书本中给有这个公式

int negate(int x) {
  return ~x + 1;
}



第三关



1.isAsciiDigit

/* 
 * 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是否是ascii码,即

0x30 <= x <= 0x39

返回1,反之则返回0

先算出0x30等于十进制的48,而0x39位十进制的57,所以转化一下,即求

x-48>=0 && x-58<0

(-运算符使用第二关第三题可以计算),再通过取反并移31位。如果x是在此区间范围,

!((x + ~48 + 1) >> 31) = 0x1

,

((x + ~58 + 1) >> 31 = 0x1

,通过&连接即可

int isAsciiDigit(int x) {
  return (!((x + ~48 + 1) >> 31)) & ((x + ~58 + 1) >> 31);
}



2.conditional

/* 
 * conditional - same as x ? y : z 
 *   Example: conditional(2,4,5) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 */

大意:实现三目运算符 x ? y : z

这题可以这样想,既然x!=0返回y,则有一部分应该是0xffffffff & y,此时应该无视z,那么另外一个部分就可以是0x0 & z,然后通过|操作符连接。即返回

(0xffffffff & y) | (0x0&z)


那么问题来了,x!=0时,则需将x转化为0xfffffff,反之则转化为0x0;我是用移位操作来完成的,类似二分的递增。

int conditional(int x, int y, int z) {
  int bits = !!x;
  bits += bits << 1;
  bits += bits << 2;
  bits += bits << 4;
  bits += bits << 8;
  bits += bits << 16;
  return (bits & y) | (~bits & z);
}

看到网上有将x转化为0xfffffff更神仙的方法:

int conditional(int x, int y, int z) {
   return ((!x+~1+1)&y)|((~!x+1)&z);
}



3.isLessOrEqual

/* 
 * 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,反正返回0

先得到x, y和 y – x 的符号位,再分三种情况讨论即可

①x < 0, y < 0, y – x >= 0

②x > 0, y > 0, y – x >= 0

③y >=0, x < 0

int isLessOrEqual(int x, int y) {
  int negX = x >> 31;
  int negY = y >> 31;
  int gte = !((y + ~x + 1) >> 31);
  return (negX & negY & gte) | (!negX & !negY & gte) | (negX & !negY);
}



第四关



1.logicalNeg

/* 
 * 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 
 */

大意:实现!的逻辑

这题大概是第四关里面最简单了的

注意一个特点, 只有+0 == -0,其他的数都不等,那么可以利用这个特点,来对x进行一个比较和判断,当 x == 0时,(~-0) & (~+0) 的符号位一定为1, 若是其他的数,符号位则不为1. 最后右移31位,&1去除其他的位可以得到结果

int logicalNeg(int x) {
  return (((~(~x + 1)) & (~x)) >> 31) & 1;
}



2.howManyBits

/* 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
 */

大意:返回最少用来表示x的位数.

可以说是最难的一题了

比如说howManyBits(12) 返回 5,12的需要最少的二进制是 01100,高位的0可以去掉,此时需要的位数是5位。但是如果用1100就不行,因为这时表示的是-4。表示-5的需要最少的二进制为 1001,返回4

可以先做一个预处理,把负的都取反,比如上面的-5,取反后为(1…1)0110,这时就可以把-5当成正数来处理了。

接下来这一部分比较头疼,我也是想了很久才想明白,主要是使用了一个二分的思想

核心思想是,查看x的后16bit是否都是1,如果是这样的话,那么x至少要16位才能表示;如果后16位都是1,那么将x右移16位,反之则右移0位,使用

(!!(x >> 16)) << 4

可以使得s16变为0或16;接下来,再依次对x的后8位,4位,2位,1位进行处理。

最后特殊情况需要注意,比如当x输入是0时,这是s1 – s16都是0,此时s16 + s8 + s4 + s2 + s1 = 0, 但实际上都需要返回1,尝试在之前的结果加上x+1,此时返回的刚好等于1;再看另一种情况, x是0x01需要返回2,此时s1 – s16均为0, 最后的x = 1,x+1 =2,也成立。所以最后应该返回

s16 + s8 + s4 + s2 + s1 + x + 1

而不是

s16 + s8 + s4 + s2 + s1

int howManyBits(int x) {
  int s16, s8, s4, s2, s1;
  int sign = (x >> 31) & 1;
  int mask = ~sign + 1;

  x = (mask & ~x) | (~mask & x);
  
  s16 = (!!(x >> 16)) << 4;
  x >>= s16;

  s8 = (!!(x >> 8)) << 3;
  x >>= s8;

  s4 = (!!(x >> 4)) << 2;
  x >>= s4;

  s2 = (!!(x >> 2)) << 1;
  x >>= s2;

  s1 = (!!(x >> 1)) << 0;
  x >>= s1;

  return s16 + s8 + s4 + s2 + s1 + x + 1;
}



3.floatScale2

/* 
 * 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
 */

大意:给定一个位表示的浮点数f,返回2*f的位表示

这里面牵扯到的是浮点数的知识了,翻书可以知道float是32位表示,其中符号位sign占1位,阶码位exp占8位,尾数frac占23位。所以可使用掩码操作,先分别得到sign,exp,frac的位表示。

接下来就很明朗了,直接将这个数分为规格化数(normalize)和非规格化数(denormolize)讨论即可,对sign,exp,frac进行计算,最后返回

sign | exp | frac

①exp = 0的时候,十进制的值表示为2



E

M

{^E*M}


















E
















M






,这时候uf*2,也就是2



E

2

M

{^E*2M}


















E
















2


M






,直接frac左移一位就可以了(非规格化的值时,M=f)。

②exp !=0 且 uf不为无穷时,十进制2



E

{^E}


















E
















M

{*M}










M






乘2就变成了2



E

+

1

{^{E+1}}



















E


+


1

















M

{*M}










M






,又



E

=

e

x

p

b

i

a

s

{E = exp – bias}







E




=




e


x


p









b


i


a


s






,所以只需要将exp加1就可以完成计算了,此外,还要注意一下溢出的情况特判。

unsigned floatScale2(unsigned uf) {
  unsigned sign = uf & 0x80000000;
  unsigned exp = uf & 0x7f800000;
  unsigned frac = uf & 0x007fffff;
  if( exp == 0x0){
    frac <<= 1;
  }else if(exp!= 0x0 && exp != 0x7f800000){
    exp += 0x00800000;
    // return ∞
    if(exp == 0x7f800000) return sign | exp;
  }

  return sign | exp | frac;
}



4.floatFloat2Int

/* 
 * 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
 */

大意:将float转化为int,其中float给出的是位表示。

还是先分别取其中的sign,exp和frac三个位。先计算出



a

b

s

=

2

E

M

abs = 2{^E}*M






a


b


s




=








2














E





















M





,然后返回

sign == 0 ? abs : -abs

;

可以先在草稿纸上算一算,uf<1时肯定要返回0,所以计算uf==1时的临界值,即



2

E

M

=

=

1

{2^E*M == 1}








2










E
















M




=


=




1






,此时E是0,根据



E

=

e

x

p

127

{E = exp – 127}







E




=




e


x


p









1


2


7






可得exp为127,所以分下面几种情况讨论

①当exp<127时,向下取整进而返回0;

②这时再来考虑一下溢出的情况,int最大值为



2

31

1

2^{31}-1







2











3


1





















1





所以这个时候



e

x

p

127

=

=

31

{exp – 127} == 31







e


x


p









1


2


7





=






=








3


1





刚好溢出,所以当exp > 157的时候返回一个0x80000000u

③最后来考虑可以正常计算的情况,先求得abs的值,然后再来考虑frac位的情况。由于



a

b

s

=

2

E

(

1

+

f

)

{abs = 2^E*(1+f)}







a


b


s




=





2










E
















(


1




+




f


)






,所以只需要在原来的基础上加上



f

2

E

{f*2^E}







f










2










E













,这就很好办了,使用一个循环,依次计算frac的每一位即可。


int floatFloat2Int(unsigned uf) {
  int sign = uf >> 31;
	int exp = (uf >> 23) & 0xff;
	int frac = uf & 0x007fffff;
	int E;
	int cnt;
	int abs;
	if (exp < 127) {
		return 0;
	}
	else if (exp > 157) {
		return 0x80000000u;
	}
	E = exp - 127;

	//2^E * M
	cnt = -23;
	abs = 1 << E;
	while (frac > 0) {
		abs += (cnt + E >= 0) ? (1 << (cnt + E)) : 0;
		cnt++;
		frac >>= 1;
	}

	return !sign ? abs : ~abs + 1;
}



5.floatPower2

/* 
 * 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^x的位级表示

也是一系列的特判,计算float可以表示的范围。

①最小的边界值为



2

E

=

2

23

126

=

2

149

2^E=2^{-23-126}=2^{-149}







2










E











=









2














2


3





1


2


6












=









2














1


4


9













,所以x<-149时直接返回0

②最大的值为



2

254

127

=

2

E

127

2^{254-127}=2^{E-127}







2











2


5


4





1


2


7












=









2











E





1


2


7













,所以x>127时返回一个正无穷就行

③这时再来计算最小的规格化值,也就是exp等于1,frac=0时x的边界值。这时十进制是



2

1

127

=

2

126

2^{1-127}=2^{-126}







2











1





1


2


7












=









2














1


2


6













,所以当x<-126时,exp=0,frac=-x

④最后的情况就是exp!=0了,这时位数frac=0,由于



2

x

=

2

e

x

p

127

{2^x = 2^{exp-127}}








2










x











=





2











e


x


p





1


2


7














,所以exp = x+127,通过掩码操作和移位,得到exp部分的位表示。

unsigned floatPower2(int x) {
    unsigned sign = 0x0;
    unsigned exp;
    unsigned frac;
    if(x < -149) return 0;
    else if(x > 127) return 0x7f800000u;
    else if( x < -126 ){
      exp = 0x0;
      frac = ~x + 1;
    }else{
      frac = 0x0;
      exp = ((x + 127) & 0xff) << 23;
    }
    return sign | exp | frac;
}



运行结果

舒服了

在这里插入图片描述

在这里插入图片描述



小结

这个实验写了两天,文章写了一天(猝。有部分题还是有难度的。推荐vscode上的一个插件Romote-SSH,通过这个插件连接远程服务器,直接在vscode上写的。


剩下的部分和书接下来再看,有一说一csapp这本书真有意思,接下来再继续完成bomb实验。



版权声明:本文为qq_38507937原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。