WHUCS-计算机系统基础(CSAPP)-Lab 1-DataLab实现

武汉大学计算机系统基础(CSAPP)课程中datalab的实现。

目录

前言

一、实验前的准备

二、实验限制

1.dlc

2.btest

三、代码撰写

1.bitOr

2.anyEvenBit

 3.rotateLeft

4.greatestBitPos 

5.leastBitPos

 6.subOK

 7.satMul3

8.divpwr2

9.float_abs 

10.float_i2f

四、编译执行

总结


前言

DataLab是《深入了解计算机系统》一书中的附带实验,是武汉大学计算机系统基础(CSAPP)课程的第一个真正意义上的实验。本实验截取了原书实验中的部分题目,是武汉大学计算机学院希冀平台上的作业。笔者在这里将我的答案和操作过程展示出来供大家参考。


一、实验前的准备

在实验前,我们需要把实验所需要的文件先下载到学院提供的Linux虚拟机上,一般压缩包会下载到/mnt/cgshare/目录中。如果你不想把实验文件复制到桌面,我们完全可以把cgshare目录当做工作目录。在桌面直接打开终端,分别输入以下几行命令:

cd /mnt/cgshare/
tar xvf datalab-handout.tar
cd datalab-handout

第一行表示打开/mnt/cgshare/目录,第二行表示对datalab-handout.tar压缩包进行解压,第三行表示打开datalab-handout目录。

如果你想把实验文件复制到桌面上(这样可能会方便找到实验文件),我们可以输入以下几行命令:

cp /mnt/cgshare/datalab-handout.tar ./
tar xvf datalab-handout.tar
cd datalab-handout

第一行表示把 /mnt/cgshare/目录中的datalab-handout.tar压缩包复制到桌面,第二行表示对datalab-handout.tar压缩包进行解压,第三行表示打开datalab-handout目录。

二、实验限制

1.dlc

打开datalab-handout目录或者在终端输入以下命令:

ls -al

此命令表示用长格式列出该目录下所有的文件及它们的详细信息。

我们会发现有一个文件名为dlc,这个程序是用来检测我们写的程序中的语句、符号、输入的数的大小是否符合实验要求(因为所有的题目都有限制),我们的代码必须符合要求才能通过dlc的检查。

2.btest

我们在输入刚才那段命令后会发现列表中还含有btest.h和btest.c文件,这两个文件在编译后用于检测我们写入函数的正确性,但是无法检测代码是否符合要求。

三、代码撰写

我们需要编写的函数全部都在bits.c文件中。我由衷的建议各位同学把这个文件下载到本地用自己电脑上的IDE编写函数,因为比起Visual Studio和VS Code,Vim真的很难用。

1.bitOr

函数功能:实现按位或运算

可用运算符:~ & 

最大操作数:8

我们发现可用运算符只给了我们~和&,而根据德·摩根定律可以通过非运算和与运算实现或运算,于是我们就可以顺理成章的写出答案。

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

2.anyEvenBit

函数功能:如果输入的数的二进制表示中有任何偶数位是1,则返回1,反之则返回0

可用运算符:! ~ & ^ | + << >>

最大操作数:12

值得注意的是,一个数的二进制表达的第n位应该这么数:

二进制表示: 1  1  0  1  0 ... 0  1  1  0  1 
位数:      32 31 30 29 28 ... 4  3  2  1  0

 我们想到可以使用掩码0x55555555与x做按位与运算就可以得出是否有偶数位为1。很可惜,由于dlc的制裁,我们不能输入一个大于255的掩码,只能通过位移构造掩码。

int anyEvenBit(int x) {
    int mask = 0x55 | 0x55 << 8;
    mask = mask | mask << 16;
    return !!(x & mask);
}

 3.rotateLeft

函数功能:循环左移,就是左移出去的n位会被补到右边

可用运算符:~ & ^ | + << >> !

最大操作数:25

我们可以先构造n位的全为1的掩码,然后通过右移运算获取x的高n位,并将其与mask进行按位与运算消去其符号位,然后通过左移运算以及按位或运算得到答案

int rotateLeft(int x, int n) {
    int mask =  (1 << n)+(~0);
    int r = (x >> (32 + (~n) + 1)) & mask;
    return ((x << n) ) | r;
}

4.greatestBitPos 

函数功能:将x的二进制表示中最高的为1的位置1,其余位置0

可用运算符:! ~ & ^ | + << >>

最大操作数:70

核心思路是通过3-7行把最高位的1扩散到这一位之后的每一位,再通过减去他自己右移一位的值来消去后边所有的1,但是这样做遇见负数很难办,会直接得到0而不是INT_MIN,所以我们通过get x的符号位并右移31位再将之与结果做按位或运算就可以得到答案。

int greatestBitPos(int x) {
    int x_s = x >> 31;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return  ((x ^ (x >> 1)) | (x_s << 31));
}

5.leastBitPos

函数功能:将x的二进制表示中最低的为1的位置1,其余位置0

可用运算符:! ~ & ^ | + << >>

最大操作数:70

这个没什么说的 ,就是通过x与-x做按位与运算,值得注意的是,本题中dlc不允许负号(或者减号)出现,而我们知道-x可以用以下位运算实现:

- x = ~ x + 1;

于是我们就可以得到整个函数的代码:

int leastBitPos(int x) {
   return x & ( ~ x + 1);
}

 6.subOK

函数功能:检测x-y是否发生溢出

可用运算符:! ~ & ^ | + << >>

最大操作数:20

根据m+n是否溢出的条件(m和n同号且m+n与m异号),我们可以类比m+n是否溢出写出x-y是否溢出的判断函数。值得注意的是,本题中dlc不允许负号(或者减号)出现,因此我们需要使用leastBitPos中表示负数的方法来实现减法。具体实现如下:

int subOK(int x, int y) {
    int res = x + (~ y + 1);
    int x_sign = x >> 31;
    int y_sign = y >> 31;
    int res_sign = res >> 31;
    return !(x_sign ^ y_sign)|!(x_sign ^ res_sign);
}

但是这样做额外声明了好几个变量用来表示x,y,x-y的符号, 我们只是用他们来比较彼此之间是否相同,完全没有必要这样做,可以一次性做异或运算之后统一右移,还可以省下几个操作数。

int subOK(int x, int y) {
    int res = x + (~ y + 1);
    return !((x ^ y)>>31)|!((x ^ res)>>31);
}

 7.satMul3

函数功能:如果x*3溢出则输出相应的INT_MAX或者INT_MIN,反之则输出x*3

可用运算符:! ~ & ^ | + << >>

最大操作数:25

由于不能使用乘法和输出大于0x55以上的数,于是采用加法构造2*x,采用1<<31构造tmin并通过加上按位取反后的x的符号位来判断输出tmin还是tmax,当然不能忘了判断x、x2、x3是否同号来选择是输出limit还是x3。具体实现如下:

int satMul3(int x) {
    int x_s = x >> 31;
    int x2 = x + x;
    int x3 = x + x2;
    int t_min = 1 << 31;
    int mask = ((x ^ x2) | (x ^ x3)) >> 31;
    return ((~mask) & x3) + (mask & (t_min + (~x_s)));
}

8.divpwr2

函数功能:输出x/(2^n),结果是向0舍入

可用运算符:! ~ & ^ | + << >>

最大操作数:15

一眼右移运算解决除以2的幂。问题在于向0舍入,我们可以通过左移取出它们的符号位的相反数,加到右移得到的结果上。具体实现如下:

int divpwr2(int x, int n) {
    int y = x >> 31;
    return(x + (y & ((1 << n) + ~0))) >> n;;
}

9.float_abs 

函数功能:输入一个无符号数,利用IEEE浮点规则将这个无符号数的二进制表达对应的单精度浮点数取绝对值,如果是NaN,则输出自己

可用运算符:所有的整数和无符号数运算符,包括 ||, &&, if, while等

最大操作数:10

在浮点数的函数中,dlc不在限制我们输入的数字。我们可以生成掩码0x7f800000,将其与x做与运算后右移判断其指数位是否全为1,通过左移运算判断其小数位是否全为0,然后根据情况返回绝对值还是自己,具体实现如下:

unsigned float_abs(unsigned uf) {
    if ((uf & 0x7f800000) >> 23 == 255 && uf << 9) return uf;
    return uf & 0x7fffffff;
}

那既然NaN指数位全为1,小数位又非全零,也就是把他的符号位修正为0之后用无符号数来看比其他任何种类的数都大,我们为什么不直接先生成绝对值,再通过比大小判断返回绝对值还是返回自己呢?具体实现如下:

unsigned float_abs(unsigned uf) {
    int ans = uf & 0x7FFFFFFF;
    if(ans > 0x7F800000)     
        return uf;
    return ans;
}

10.float_i2f

函数功能:输入一个整数,输出它被强制类型转换为单精度浮点数之后的二进制表达,用无符号数表示

可用运算符:所有的整数和无符号数运算符,包括 ||, &&, if, while等

最大操作数:30

 这个就略有些麻烦。将整数x转换为单精度浮点数的二进制位表示,通过符号位处理、规格化调整阶码(计算指数偏移后的enow)和尾数(截取有效位fnow),并应用向偶舍入规则处理尾数低位tail,最终拼接符号、阶码、尾数三部分生成浮点编码。

unsigned float_i2f(int x) {
    unsigned sign = 0, enow = 0, fnow = 0, absx = x,
        shiftLeft = 0, tail = 0, result = 0;
    unsigned pos = 1 << 31;
    if (x == 0) {
        return 0;
    }
    else if (x < 0) {
        absx = -x;
        sign = pos;
    }
    while ((pos & absx) == 0) {
        absx <<= 1;
        shiftLeft += 1;
    }
    enow = 127 + 31 - shiftLeft;
    tail = absx & 0xff;
    fnow = (~(pos >> 8)) & (absx >> 8);
    result = sign | (enow << 23) | fnow;
    if (tail > 0x80) {
        result += 1;
    }
    else if (0x80 == tail) {
        if (fnow & 1) {
            result += 1;
        }
    }
    return result;

四、编译执行

我们把编写好的bits.c文件上传到虚拟机后,打开终端,输入以下命令:

cd /mnt/cgshare/
mv -f bits.c datalab-handout
cd datalab-handout

第一行表示打开/mnt/cgshare/目录,第二行表示强制覆盖原来datalab-handout目录中的bits.c文件,第三行表示打开datalab-handout目录。

然后我们输入执行dlc的命令,它会检查我们是否用了限制之外的运算符等。

./dlc

没错的话,应该是0 error。

然后我们编译执行btest,它会检查我们的函数功能是否正确 。输入以下命令:

make btest
./btest

值得注意的是,如果你第一次上传的bits.c文件没写对,第二次上传时要清空编译文件。输入以下命令:

make clean

然后重新编译执行btest即可。 


总结

以上就是我在完成datalab时的详细做法。我把每一步都尽可能讲的较为详细,以便初次接触这门课的同学能够快速上手。另外这也是本人第一次在CSDN上写blog,如果有不清楚或者不满意的地方欢迎各位在评论区交流。

你可能感兴趣的:(linux,CSAPP,C语言,课程实验)