CSAPP:DataLab实验

前言

  • 本实验是《深入理解计算机系统》一书中的附带实验。在本次实验中,学生实现简单的逻辑,二进制补码和浮点函数,但使用C的高度受限的子集。例如,可能会要求他们仅使用位级操作和直线代码来计算数字的绝对值。
  • 本实验帮助学生了解C数据类型的位级表示和数据操作的位级行为。
  • 本文用于记录之前做实验的一些信息,可能思路有些凌乱,谨慎参考

实验内容及操作步骤

  1. 填写 bits.c里面的函数,使其按照规定的要求(比如只能使用有限且规定的操作符和数据类型,不能使用控制语句等等)实现函数的功能。
  2. 同时 dlc文件是用来检测 bits.c 里面的函数是否是按照要求编写的,有没有使用非法的数据类型等。 使用方法:./dlc bits.c
  3. 检测成功后,使用 btest 测试 每一个函数功能方面是否正确无误。使用方法:./btest,如果某个函数错误,会显示错误的数据,以及正确的数据。

操作步骤

一、安装dlc

由于本次实验基于dlc对bits.c进行编译,故需要先将老师给的文件夹datalab-handout添加到linux环境下,之后的实验将会基于这个文件夹进行操作

二、阅读引导以及注意事项

【datalab-handout下的README文件】


CS:APP数据实验室
给学生的路线


你的目标是修改你的bits.c副本,以便它通过所有
在不违反任何编码指南的情况下在btest中进行测试。


0.文件:


Makefile - 创建btest,fshow和ishow
自述文件 - 此文件
bits.c - 您将修改和处理的文件
bits.h - 头文件
btest.c - 主要的btest程序
btest.h - 用于构建btest
decl.c - 用于构建btest
tests.c - 用于构建btest
tests-header.c-用于构建btest
dlc * - 规则检查编译器二进制文件(数据实验室编译器)
driver.pl * - 使用btest和dlc来自动编程bits.c的驱动程序
Driverhdrs.pm - 可选“Beat the Prof”比赛的头文件
fshow.c - 用于检查浮点表示的实用程序
ishow.c - 用于检查整数表示的实用程序


  1. 修改bits.c并检查它是否符合dlc

重要信息:在开始之前请仔细阅读bits.c文件中的说明;这些给出了获得满分你所需要遵循的编码规则
使用dlc编译器(./dlc)自动检查您的版本
bits.c是否符合编码指南:

unix> ./dlc bits.c

如果您的代码没有问题,dlc将以默认方式返回。否则,它会打印标记任何问题的消息。
用dlc运行-e开关:

unix> ./dlc -e bits.c

使dlc打印每个函数使用的运算符数。
获得合法解决方案后,您可以使用./btest程序来测试其正确性


  1. 用btest测试

此目录中的Makefile将编译您的bits.c版本以及附加代码用于创建名为btest的程序(或测试工具)
要编译并运行btest程序,请输入:

	unix> make btest
    unix> ./btest [可选的cmd行args]

每次更改位时都需要重新编译btest程序。当你从一个平台移动到另一个平台时,你会想要摆脱旧版本的btest并生成一个新版本。使用命令:

	unix> make clean
    unix> make btest

Btest通过运行数百万次测试来测试代码的正确性
每个功能的案例。它测试着名角落周围的宽幅,例如Tmin和整数谜题为零,以及零,inf和浮动的非规格化数和规格化数之间的边界点难题。当btest检测到您的某个功能出错时,它打印出失败的测试、错误的结果和预期结果,然后终止该功能的测试。
以下是btest的命令行选项:

unix> ./btest -h

用法:./ btest [-hg] [-r <n>] [-f <名称> [-1 | -2 | -3 <val>] *] [-T <时间限制>]

-1 指定第一个函数参数
-2 指定第二个函数参数
-3 指定第三个函数参数
-f 仅测试指定的函数
-g用于自动编辑的格式输出,没有错误消息
-h打印此消息
-r 为所有问题给出均匀的n重量
-T 将超时限制设置为lim

例子:
测试所有函数的正确性并打印出错误消息:

 unix> ./btest

以紧凑的形式测试所有函数,没有错误消息:

 unix> ./btest -g

测试函数foo的正确性:

unix> ./btest -f foo

使用特定参数测试函数foo的正确性:

unix> ./btest -f foo -1 27 -2 0xf

Btest不会检查您的代码是否符合编码要求准则。使用dlc来做到这一点。


3.助手计划


我们已经包含了ishow和fshow程序来帮助您破译,分别表示整数和浮点数据。每个需要一个单个十进制或十六进制数作为参数。要构建它们:

unix> make

示例用法:

unix> ./ishow 0x27

十六进制= 0x00000027,有符号= 39,无符号= 39

 unix> ./ishow 27

十六进制= 0x0000001b,有符号= 27,无符号= 27

unix> ./fshow 0x15213243

浮点值3.255334057e-26,位表示0x15213243,符号= 0,指数= 0x2a,分数= 0x213243
标准化: +1.2593463659 X 2 ^( - 85)

linux> ./fshow 15213243

浮点值2.131829405e-38,位表示0x00e822bb,符号= 0,指数= 0x01,分数= 0x6822bb
标准化: +1.8135598898 X 2 ^( - 126)

【datalab-handout下的bits.c文件中的引导以及注意事项】

第1步:仔细阅读以下说明。
您将通过编辑此源文件中的函数集合为数据实验室提供解决方案
整数编码规则:
用一个替换每个函数中的“return”语句或更多实现该功能的C代码行。你的代码必须符合以下风格:

int Funct(arg1,arg2,...{
      / *您的实施如何运作的简要说明* /
      int var1 = Expr1;
      ...
      int varM = ExprM;
      varJ = ExprJ;
      ...
      varN = ExprN;
      返回ExprR;
  }

每个“Expr”都是一个表达式,仅使用以下内容:

  1. 整数常量0到255(0xFF),包括0和255。不允许使用像0xffffffff这样的大常量。
  2. 函数参数和局部变量(没有全局变量)。
  3. 一元整数运算! 〜
  4. 二进制整数运算&^ | + << >>

一些问题进一步限制了允许的运算符集合。
每个“Expr”可能包含多个运算符。你不局限于每行一个操作数。
明确禁止您:

  1. 使用任何控制结构,如if,do,while,for,switch等。
  2. 定义或使用任何宏。
  3. 在此文件中定义任何其他功能。
  4. 调用任何功能。
  5. 使用任何其他操作,例如&&,||, - 或?:
  6. 使用任何形式的转换
  7. 使用除int之外的任何数据类型。这意味着你不能使用数组,结构体或者共用体。

你可以假设你的机器:

  1. 使用2进制补码,32位整数表示
  2. 算术上进行右移。 //右移操作都为算术移位
  3. 将整数移动比字大的位数时具有不可预测的行为
    可接受的编码样式示例:
    / *
  • pow2plus1 - 返回2 ^ x + 1,其中0 <= x <= 31
  • /
 int pow2plus1(int x){
     / *利用移位能力计算2 * /的功率
     return1 << x)+ 1;
  }

/ *

  • pow2plus4 - 返回2 ^ x + 4,其中0 <= x <= 31
  • /
 int pow2plus4(int x){
     / *利用移位能力计算2 * /的功率
     int result =1 << x);
     result+ = 4;
     return result;
  }

浮点编码规则
对于需要您实现浮点运算的问题,编码规则不太严格。你被允许使用循环和条件语句。您可以同时使用整型和无符号整型;可以使用任意整数和无符号常量;可以使用循环和控制语句,使用INT和无符号
明确禁止您:

  1. 定义或使用任何宏
  2. 在此文件中定义任何其他函数
  3. 调用任何函数
  4. 使用任何形式的转换
  5. 使用除int或unsigned之外的任何数据类型。这意味着你不能使用数组,结构体,共用体
  6. 使用任何浮点数据类型,操作或常量

笔记:

  1. 使用dlc(数据实验室检查器)编译器(在讲义中描述)检查解决方案的合法性。
    使用DLC检查合法性
  2. 每个函数都有最大数量的运算符(!〜&^ | + << >>)您可以使用它来实现该功能;dlc检查最大操作数。请注意,’='不是计算;您可以根据需要使用尽可能多的这些而不会受到惩罚。
    每个函数都有所使用的操作符有限,“=”不算。
  3. 使用btest测试线束检查功能是否正确。
  4. 使用BDD检查程序正式验证您的功能
  5. 每个函数的最大操作数在函数中给出每个函数的标题注释。如果在写入和此文件中的最大操作数之间有任何不一致之处,请考虑这个文件是权威来源。

/ *
*步骤2:根据编码规则修改以下功能。
*
*重要。为了避免分级惊喜:

    1. 使用dlc编译器检查您的解决方案是否符合要求编码规则。
    1. 使用BDD检查程序正式验证您的解决方案是否生成正确的答案。
  • /

三、函数实现

  1. bitAnd函数
/* 
 * bitAnd - x&y using only ~ and | 使用~和|实现位与操作
 *   Example: bitAnd(6, 5) = 4
 *   Legal ops: ~ |
 *   Max ops: 8
 *   Rating: 1  
 */
int bitAnd(int x, int y) {
  //使用摩根定律 a&b=~(~a|~b)
  return ~(~x|~y);
}
  1. getByte函数
/*  
 * getByte - Extract byte n from word x  从整型x中取出第n个字节
 *   Bytes numbered from 0 (LSB) to 3 (MSB)
 *   Examples: getByte(0x12345678,1) = 0x56
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 6
 *   Rating: 2
 */
int getByte(int x, int n) {
  //从整型x中取出第n个字节,只需要x往右移n个字节(即右移n*8位)之后再&上0xFF即可
  return (x>>(n<<3)) & 0xFF;
}
  1. logicalShift函数
/* 
 * logicalShift - shift x to the right by n, using a logical shift 实现逻辑右移
 *   Can assume that 0 <= n <= 31
 *   Examples: logicalShift(0x87654321,4) = 0x08765432
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 3 
 */
int logicalShift(int x, int n) {
  //要实现逻辑右移的话,需要将算术右移得到的值&上00…11…(前面有n个零,后面都为1)
  int arithShift=(x>>n);	//先将x算术右移n位
  int mask=((~(1<<31)>>n)<<1)+1;	//产生一个mask值,补齐左移时添加的0,相当于右移n-1位
  return arithShift & mask;
}
  1. bitCount函数
  /*
 * bitCount - returns count of number of 1's in word 求x中1的个数
 *   Examples: bitCount(5) = 2, bitCount(7) = 3
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 40
 *   Rating: 4
 */
int bitCount(int x) {
  //本题采用分治的方法,先求出两个一组中1的个数,再求的4、8、16一组中1的个数
  //将x的bit表示,分成两个一组,组内一位相加。
  //再将x1的bit表示,分成四个一组,同样组内两位相加
  //依次类推…
  //注意:最后三次操作没有先经过mask,而是直接相加再mask,不是因为遵守分配律,
  //而是因为前面2次要先mask再相加是因为算术右移产生1,要先把它mask成0。
  //而经过第二次的四位一组,组内2位两位相加,表示bit 1的数量肯定不会超过4,即四位一组表示成0100。
  //这样进行算术右移不会产生1,所以为了减少运算次数,可以先相加再mask
  int mask1;
  int mask2;
  int mask4;
  int mask8;
  int mask16;

  mask1 = 0x55 | 0x55 << 8;
  mask1 = mask1 | mask1 << 16;
  mask2 = 0x33 | 0x33 << 8;
  mask2 = mask2 | mask2 << 16; 
  mask4 = 0x0F | 0x0F << 8;
  mask4 = mask4 | mask4 << 16;
  mask8 = 0xFF | 0xFF << 16;
  mask16 = 0xFF | 0xFF << 8;

  x = (x & mask1) + ((x >> 1) & mask1);
  x = (x & mask2) + ((x >> 2) & mask2);
  x = (x + (x >> 4)) & mask4;
  x = (x + (x >> 8)) & mask8;
  x = (x + (x >> 16)) & mask16;

  return x;
}
  1. bang函数
/* 
 * bang - Compute !x without using ! 不用!符号求得!x
 *   Examples: bang(3) = 0, bang(0) = 1
 *   Legal ops: ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 4 
 */
int bang(int x) {
  //利用了补码相反数的特性,除了0,与0x80000000之外,其他的数与其相反数的符号肯定是不同的;
  //0x80000000的相反数是它本身,所以只有0|0的相反数的最高位是0,其他数|相反数的最高位是1
  //若(x|(~x+1))如果不是0的话,右移31位会得到0xFFFFFFFF,如果是0,则会得到0x0,最后加1即可得到!x
  return ((x|(~x+1))>>31)+1;
}
  1. tmin函数
 /* 
 * tmin - return minimum two's complement integer//返回补码整数的最小整数数值 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void) {
  //补码整数的最小整数数值就是0x80000000,因此将1左移31位即可
  return (1<<31);
}
  1. fitsBits函数
  /* 
 * fitsBits - return 1 if x can be represented as an 
 *  n-bit, two's complement integer.如果x可以表示为n位二进制补码形式则返回1
 *   1 <= n <= 32
 *   Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 2
 */
int fitsBits(int x, int n) {
	//想要判断一个数是否能用更少的位数n来判断,只需要判断将该数进行符号扩展(先左移再右移)
	//之后得到的数与原来的数是否相等(用异或来判断),若相等则返回1,否则为0
  int shiftNumber = 32+(~n)+1;    //32-n等于32+(n)'s
  return !(x^((x<<shiftNumber)>>shiftNumber));
}
  1. divpwr2函数
 /* 
 * divpwr2 - Compute x/(2^n), for 0 <= n <= 30 计算x除以2的n次方
 *  Round toward zero
 *   Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 2
 */
int divpwr2(int x, int n) {
   //如果x为正数,则直接右移n位,即偏移量为0;如果x为负数则需要加偏移量2^n-1之后再右移n位
   int signx=(x>>31);	
   int bias=signx & ((1<<n)+(~0)); //其中~0为-1
   return (x+bias)>>n;
}
  1. negate函数
  /* 
 * negate - return -x 求相反数
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 *   按位取反,末位加1
 */
int negate(int x) {
  return (~x)+1;
} 
  1. isPositive函数
  /* 
 * isPositive - return 1 if x > 0, return 0 otherwise 如果x为正数则返回1,否则返回0
 *   Example: isPositive(-1) = 0.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 8
 *   Rating: 3
 */
int isPositive(int x) {
  //返回0的有两种情况,一种是x为零,一种为负数
  //判断是否为负数时之需要判断符号位,因此可以进行符号扩展之后右移31,判断结果是否为1则为负数
  return !((!x)|(x>>31));
}
  1. isLessOrEqual函数
 /* 
 * isLessOrEqual - if x <= y  then return 1, else return 0 判断x<=y则返回1,否则0
 *   Example: isLessOrEqual(4,5) = 1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 24
 *   Rating: 3
 */
int isLessOrEqual(int x, int y) {
  //要判断x<=y通常我们都会想到用y-x>=0来判断,这时候就碰到了一个问题,如果x与y异号且x为负时会发生溢出
  //因此分情况讨论,如果x与y同号的话,看y-x的符号位(进行符号扩展右移31位)
  //如果x与y异号的话,之需要根据x来判断,若x的符号位为1则y>=x
  int signx=(x>>31) & 0x1;	//求得x的符号位
  int signy=(y>>31) & 0x1;	//求得y的符号位
  int is_notSame=(signx ^ signy);	//判断x、y是否异号,进行异或操作,如果结果为1则异号,否则同号
  int tmp=((y+(~x)+1)>>31) &0x1;	//x与y同号时进行减法操作之后得到结果的符号位
  return (((!tmp) & (!is_notSame))|(is_notSame & signx));
}
  1. ilog2函数
  /*
 * ilog2 - return floor(log base 2 of x), where x > 0 求以2为底x的对数
 *   Example: ilog2(16) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 90
 *   Rating: 4
 */
int ilog2(int x) {
  //要求以2为底,x的对数,只需要知道最高的1在哪一位即可
  //采用二分法,第一次右移16位,判断结果是否大于0,进行两次!!变为0/1之后左移4位
  //第二次右移8+bias位,判断结果是否大于零,进行两次!!变为0/1之后左移3位
  //依次分下去
  int bitsNumber;
  bitsNumber = (!!(x>>16)) << 4;
  bitsNumber = bitsNumber + ((!!(x >> (bitsNumber + 8))) << 3);
  bitsNumber = bitsNumber + ((!!(x >> (bitsNumber + 4))) << 2);
  bitsNumber = bitsNumber + ((!!(x >> (bitsNumber + 2))) << 1);
  bitsNumber = bitsNumber + ( !!(x >> (bitsNumber + 1)));
  return bitsNumber;
}
  1. float_neg函数
 /* 
 * float_neg - Return bit-level equivalent of expression -f for
 *   floating point argument f.        返回和浮点数参数-f相等的二进制
 *   Both the argument and result are passed as unsigned int's, but
 *   they are to be interpreted as the bit-level representations of
 *   single-precision floating point values.
 *   参数和返回结果都是无符号整数,但是可以解释成单精度浮点数的二进制表示
 *   When argument is NaN, return argument.
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 10
 *   Rating: 2
 */
unsigned float_neg(unsigned uf) {
 //在最高位置为0情况下,如果大于0x7F800000,一定是NaN,直接返回。如果不大于符号为取反,返回。
 unsigned tmp;
 unsigned result;
 result = uf ^ (0x80 <<24);	//让符号位取反
 //如果最高位置为0时,需要判断它与0x7F800000的大小
 tmp = uf & 0x7FFFFFFF;
 if(tmp > 0x7F800000) result = uf;
 return result;
}
  1. float_i2f函数
 /* 
 * float_i2f - Return bit-level equivalent of expression (float) x 返回int x的浮点数的二进制形式	 
 *   Result is returned as unsigned int, but
 *   it is to be interpreted as the bit-level representation of a
 *   single-precision floating point values.	返回的是unsigned型但是表示的时二进制单精度形式
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 30
 *   Rating: 4
 */
unsigned float_i2f(int x) {
  unsigned shiftLeft;
  unsigned afterShift,tmp;
  unsigned absX;
  unsigned sign;
  unsigned flag;
  absX = x;
  shiftLeft = 0;
  sign = 0;
  if(!x) return 0;
  if(x<0)
  {
	sign = 0x80000000;	//先取其符号位
   	absX = -x;
  }
  //再将剩余部分全部取为正数形式,即absx,即可以得到无符号的数值
  //然后将有数字的部分直接移动到最高位,记录移动的位数,再将其移动9位(因尾数只要23即可)。
  afterShift = absX;
  while(1)
  {
	tmp = afterShift;
	afterShift <<= 1;	//左移1位
	shiftLeft++;
	if(tmp & 0x80000000)	break;
  }
  //对于阶码部分,所以E=32-shiftleft,bias为127,加上为159,if部分做舍入处理
  if((afterShift & 0x01ff) >0x0100)
	flag = 1;
  else if((afterShift & 0x03ff) ==0x0300)
	flag = 1;
  else
	flag = 0;
  return (sign |(afterShift >> 9)|((159-shiftLeft) << 23)) + flag;
}
  1. float_twice函数
 /* 
 * float_twice - Return bit-level equivalent of expression 2*f for
 *   floating point argument f.   返回以unsinged表示的浮点数二进制的二倍的二进制unsigned型
 *   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
 */
unsigned float_twice(unsigned uf) {
  //如果阶码为0,那么就是非规格数,直接将尾数左移1位到阶码域上,其他不变即可
  //例如 0 00000000 1000…001 变成 0 00000001 000…0010
  //这样可以做的原因正是由于非规格化数的指数E = 1 - bias,而不是-bias
  //这样使得可以平滑地从非规格数过渡到规格化数。 
  //如果阶码不为0且不是255,那么直接阶码加1即可。 
  //如果阶码为255,那么是NaN,∞,-∞,直接返回。
  if((uf & 0x7F800000)==0)
  uf = (((uf & 0x007FFFFF)<<1)|(uf & 0x80000000));
  else if((uf & 0x7F800000)!=0x7F800000)
  uf = uf + 0x00800000;
  return uf;
}

实验结果及分析

一、将目录切换到dlc所在的文件下,输入./dlc bits.c对bits.c文件中的函数进行编译,检查错误
在这里插入图片描述
由此可以看出,我的函数中使用了没有定义的变量signy
在这里插入图片描述
将sighy改为signy之后再使用dlc对bits.c进行编译,编译通过,即书写合法
在这里插入图片描述
二、输入./dlc -e bits.c使dlc打印每个函数使用的运算符数。
在这里插入图片描述
经验证,各函数使用的操作数符合要求
三、用btest测试,输入make来编译btest
在这里插入图片描述
可知此时以产生了可执行文件fshow和ishow
四、使用./btest来检查测试所有函数的正确性并打印出错误消息;输入./btest -g以紧凑的形式测试所有函数,不会显示错误消息
在这里插入图片描述
紧凑形式输出
在这里插入图片描述
可以看出此时bitCount函数有错误,使用 ./btest -f foo测试函数foo的正确性;
在这里插入图片描述
将错误改正之后再次检查,正确
在这里插入图片描述
可以使用./ishow./fshow 十六进制来显示计算机中表示的整型或浮点型数值
在这里插入图片描述

  • 34
    点赞
  • 188
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值