还是那句话,千万不要慌,千万不要着急,耐下性子慢慢来,一步一个脚印,把基础打的牢牢的,一样不比那些人差。回到实验本身,自从按照西工大计算机学院计算机系统基础实验一(环境配置)-CSDN博客 这篇博客结束完成虚拟机平台VMware 17 Workstation Pro的安装,以及Ubuntu虚拟机的导入,还有实验包的导入之后,我们看到了如下图所示的实验压缩包content_1701767947881.tar,如 图1:查看被导入到Ubuntu虚拟机的实验包的压缩包content_1701767947881.tar 所示。接着我们解压缩这个包,如 图2:解压缩实验压缩包content_1701767947881.tar 所示。
(图1:查看被导入到Ubuntu虚拟机的实验包的压缩包content_1701767947881.tar)
(图2:解压缩实验压缩包content_1701767947881.tar。使用"tar xvf content_1701767947881.tar"命令解压实验压缩包之后,得到了lab1-handout文件夹)
接着进入到lab1-handout文件夹,并且查看该lab1-handout文件夹下有什么文件,如 图3:进入lab1-handout文件夹,查看该文件夹下的文件 所示。 但是千万不要急着,什么都不懂就开始做,先耐下性子来慢慢读一读README文件,了解清楚lab1-handout这个文件夹是用来干什么的,如 图4:使用less命令查看README文件 、图5:查看README文件的第一页、图6:查看README文件的第二页 所示。这其中可能有很多大家不熟悉的命令,但千万不要排斥它,跟着作者一步一步来就可以。
(图3:进入lab1-handout文件夹,查看该文件夹下的文件)
(图4:使用less命令查看README文件)
(图5:查看README文件的第一页)
(图6:查看README文件的第二页)
在这里不要害怕怎么读,不要害怕说因为它是英文,所以就感到害怕。不要害怕,耐下性子慢慢读,就可以读出来一二的。在这里它的大概意思是,我们需要编写bits.c文件中的几个函数的函数体,接着使用dlc可执行文件检查bits.c文件中我们所写的函数是否满足运算符等的要求,然后使用make clean命令清除掉上一次已生成的可执行文件,再使用make btest文件生成可执行文件,最后运行生成的可执行文件价差我们所写的函数的正确性。你可能听不懂这段话是什么意思,不要怕,你跟着作者做,后面都会详细的讲到的。接着使用同样的方法,我们来查看bits.c文件,如 图7:查看bits.c文件(上)、图8:查看bits.c文件(下) 所示。
(图7:查看bits.c文件(上))
(图8:查看bits.c文件(下))
bits.c文件开头的一大段注释给我们三个要点信息:
要点1:不要使用"#include <stdio.h>",我们可以得到要点2与要点3:
要点2:对于整型数,只能使用& ^ | + << >> ! ~这八个运算符,只能定义并使用int型的局部变量以及函数参数变量,只能使用0x0到0xFF的int型常量;
要点3:对于浮点数,只能使用& ^ | + << >> ! ~这八个运算符,可以定义并使用int型或unsigned int型的局部变量以及函数参数变量,可以使用任意有符号整型与无符号整型常量,可以使用循环与条件判断。
大家记住它就好,不要深究,考虑到有些同学的Ubuntu没有内置VS code,而且vim编辑器对于初学者来说难度也有点大,所以我们选择Ubuntu内置的文本编辑器,开始编辑bits.c中的第一个函数,如 图9:使用Ubuntu内置的文本编辑器,开始编辑bits.c中的第1个函数 所示。如果文本编辑器的大小过小,可以按照 图10:调大文本编辑器显示文字的大小(上) 、图11:调大文本编辑器显示文字的大小(下) 所示。
(图9:使用Ubuntu内置的文本编辑器,开始编辑bits.c中的第1个函数)
(图10:调大文本编辑器显示文字的大小(上))
(图11:调大文本编辑器显示文字的大小(下))
定位到第174行,我们开始编写第一个函数bitXor,如 图12:定位到第174行,开始编写第1个函数bitXor 所示。
(图12:定位到第174行,开始编写第1个函数bitXor)
bitXor的意思是什么呢?意思是,需要我们只使用取反和位与运算,来实现异或操作。即只使用~与&,来实现^操作。那么该怎么实现这个功能呢?根据异或操作的定义,相同为0,不同才为1。也就是说,0^0=0,0^1=1,1^0=1,1^1=0。观察这个例子,我们发现,只要其中有并且只有1个为1即可。即~x&y和x&~y一个需要为1,一个需要为0,可以写成(~x&y)|(x&~y),但是不允许使用位或操作,那么我们应该怎么做呢?再思考下去就很麻烦了。我们不妨换一种思路,当x&y与~x&~y都为0时,这时只对应着两种情况,0^1=1与1^0=1,然后对x&y和~x&~y取反,得到~(x&y)和~(~x&~y),这时只有当0^1=1与1^0=1这两种情况下,~(x&y)和~(~x&~y)才都为1,进而~(x&y)&~(~x&~y)才为1,所以只需要写一个语句 "return ~(x&y)&~(~x&~y)"即可。如 图13:编写完成第1个函数bitXor 所示。接着保存对bits.c文件的修改,然后在包含bits.c文件的文件夹中打开命令行,如 图14:在包含bits.c文件的文件夹中打开命令行 所示。接着使用命令"./dlc -e bits.c"查看运算符的数量是否满足要求以及是否满足其它的要求。如果除了对各个函数所使用到的运算符进行计数之后就没有其它的输出,那么表明满足运算符和其它的要求。接着使用"make clean"命令清除掉上一次遗留的旧的文件,如 图15:使用命令"./dlc -e bits.c"和命令"make clean" 所示。接着再使用命令"make btest"生成新的文件,生成btest这个新的可执行文件之后,使用命令“./btest -f bitXor”来检验我们所写的第一个函数bitXor的正确性,其中"-f"选项制定了需要被检验的函数名,在这里需要被检验的函数名是bitXor。如 图16:使用命令"make btest"和命令“./btest -f bitXor” 所示。我们已成功编写了bitXor函数。
(图13:编写完成第1个函数bitXor)
(图14:在包含bits.c文件的文件夹中打开命令行)
(图15:使用命令"./dlc -e bits.c"和命令"make clean" 。使用命令"./dlc -e bits.c"查看运算符的数量是否满足要求以及是否满足其它的要求。如果除了对各个函数所使用到的运算符进行计数之后就没有其它的输出,那么表明满足运算符和其它的要求。接着使用"make clean"命令清除掉上一次遗留的旧的文件)
(图16:使用命令"make btest"和命令“./btest -f bitXor”)
接着第二个函数,isZero,当x为0时,返回1,当x不为0时,返回0,所以只需要简单的写"return !x"即可,如 图17:编写第2个函数isZero 和 图18:重复编写第1个函数时的操作 所示。
(图17:编写第2个函数isZero)
(图18:重复编写第1个函数时的操作。使用命令"./dlc -e bits.c"统计各个函数使用到的运算符的个数,使用命令"make clean"清除掉上一次遗留下来的旧的可执行文件,使用命令"make btest"生成新的btest可执行文件,使用命令"./btest -f isZero"检验所写函数isZero的正确性)
第三个函数,thirdBits,要我们自己产生一个int型的数,保证它从最低有效位开始,每第三个比特位被设置为1,即期望我们返回的数是:0100 1001 0010 0100 1001 0010 0100 1001B。但是根据要点二中的一句话:只能使用0x0到0xFF的int型常量,即我们不能直接返回它。那么应该怎么产生这个数呢?我们发现,可以先产生一个小于0xFF的int型常量,让它既满足从最低有效位开始,每第三个比特位被设置为1,又满足小于0xFF,而这个数就是0x49,它的二进制表示形式为0100 1001。接着让0x49左移9位,得到了1001 0010 0000 0000,左移9位的目的就是为了让其既能保证,除了低9位以外,从最低有效位开始,每第三个比特位被设置为1,又是0x49左移所能完成这个任务的极限。接着将0x49与0x49<<9拼凑在一起,得到了0000 0000 0000 0000 1001 0010 0100 1001,它结合了0x49和0x49<<9的优点,保证了最大程度上从最低有效位开始,每第三个比特位被设置为1。但是,最高的16个比特位还没有被处理,而这可以简单的通过((0x49<<9)+0x49)<<18来实现。最后将0x49、0x49<<9与((0x49<<9)+0x49)<<18拼凑起来,即为最终的答案。而为了节省运算符,我们使用int型变量t来保存((0x49<<9)+0x49),接着"return t+(t<<24)"即可。具体的五个步骤为:
-
期望的输出: 0100 1001 0010 0100 1001 0010 0100 1001
-
第一个步骤: 0000 0000 0000 0000 0000 0000 0100 1001 0x49
-
第二个步骤: 0000 0000 0000 0000 1001 0010 0000 0000 0x49<<9
-
第三个步骤: 0000 0000 0000 0000 1001 0010 0100 1001 (0x49<<9)+0x49
-
第四个步骤: 0100 1001 0010 0100 0000 0000 0000 0000 ((0x49<<9)+0x49)<<18
-
第五个步骤: 0100 1001 0010 0100 1001 0010 0100 1001 (((0x49<<9)+0x49)<<18)+(0x49<<9)+0x49
如 图19:编写第3个函数thirdBits 所示。
(图19:编写第3个函数thirdBits)
第4个函数anyOddBit,如果x中,只要有一个第奇数个比特位被设置为了1,那么就返回1,否则返回0,比如7是0111,其第0、1、2个比特位为1,满足第1个比特位被设置为1,返回1;比如5是0101,第0、2个比特位为1,不满足第奇数个比特位被设置为1,也就是第1个比特位没有被设置为1,所以返回0,注意这里特别容易混淆,作者因为搞混了这一点,废了好多功夫。我们的思路就是,先产生1010 1010 1010 1010 1010 1010 1010 1010,这个数是0xAAAAAAAA,它第1、3、5、7、9、11... ...31个比特位都被设置为了1,然后将0xAAAAAAAA与x做位与操作,这样子获取x的所有奇数比特位,如果x中存在一个奇数比特位被设置为1,那么位与的结果就不为0,此时连用两个逻辑非!,即可得到1,如果x中所有奇数比特位都是0,那么位与的结果就是0,此时连用两个逻辑非!,即可得到0。那么我们该如何得到0xAAAAAAAA呢?可以按照下面的五个步骤来得到0xAAAAAAAA。为了节省运算符,引入int a=0xAA,int b=0xAA<<8,int c=a+b,int d=(c<<16)+c,这时d即为0xAAAAAAAA。然后编写第四个函数anyOddBit,如 图20:编写第4个函数anyOddBit 所示。
-
期望的输出: 1010 1010 1010 1010 1010 1010 1010 1010 0xAAAAAAAA
-
第一个步骤: 0000 0000 0000 0000 0000 0000 1010 1010 0xAA
-
第二个步骤: 0000 0000 0000 0000 1010 1010 0000 0000 0xAA<<8
-
第三个步骤: 0000 0000 0000 0000 1010 1010 1010 1010 (0xAA<<8)+0xAA
-
第四个步骤: 1010 1010 1010 1010 0000 0000 0000 0000 ((0xAA<<8)+0xAA)<<16
-
第五个步骤: 1010 1010 1010 1010 1010 1010 1010 1010 (((0xAA<<8)+0xAA)<<16)+((0xAA<<8)+0xAA)
(图20:编写第4个函数anyOddBit)
第5个函数,isEqual,如果两个int型数相等,那么返回1,否则返回0,我们直接!(x^y)即可。如 图21:编写第5个函数isEqual 所示。
(图21:编写第5个函数isEqual)
第6个函数,leastBitPos,可以理解为,找到从左到右最后一个1的位置,然后其它位置都设为0。对于96来说,96可以写成0110 0000,最低位的1是从右往左第5个,所以返回0010 0000;对于104来说,104可以写成0110 1000,从左到右最后一个1的位置是第3个,所以返回0000 1000。那么我们该怎么做呢?发现可以将x各位取反然后加1,并将这个结果与x进行位与,这时返回的结果即为我们期望中的结果。比如对于96,也就是0110 0000来说,各位取反得到了1001 1111,再加1得到了1010 0000,然后将这个数与0110 0000进行位与操作,得到了0010 0000,正好是我们想要的结果,所以我们"return ((~x)+1)&x;"即可,如 图22:编写第6个函数leastBitPos 所示。
(图22:编写第6个函数leastBitPos)
接着第7个函数,isPositive, 如果x大于0的话返回1,如果x不大于0的话返回0。该怎么做呢?只需要判断符号位即可。该怎么获取符号位呢?可以将x右移31位,因为一方面针对int型变量的右移都是算术右移,另外一方面实验也规定了所有的右移都是算术右移,所以我们将x右移31位,这样所有的比特位都和符号位是一样的取值了。这时得到!(x>>31),如果x为正,则符号位为0,x>>31为0,逻辑非后为1;如果x为负,则符号位为1,x>>31为0xFFFFFFFF,逻辑非后为0。另外考虑到当x为0时,本想让其返回0,结果此时!(x>>31)的结果也是1,所以需要修正,修正的结果为!(x>>31)&!!x,进而可以写成!(!x|x>>31)。当x为0时,!x为1,!x|x>>31直接为1,!(!x|x>>31)为0;当x为负数时,!x为0,x>>31为0xFFFFFFFF,!(!x|x>>31)为0;当x为正数时,!x为0,x>>31也为0,进而!(!x|x>>31)为1,按照这个思路,我们写出了"return !(!x|x>>31)",如 图23:编写第7个函数isPositive 所示。
(图23:编写第7个函数isPositive)
第8个函数,ezThreeFourths,开始慢慢变复杂了,我们需要完美复现C语言表达式x*3/4,那么该怎么做呢?首先计算x*3,x*3可以用位运算表示为(x<<1)+x,接着计算x/4,这里并不能简单的((x<<1)+x)>>2。为什么呢?因为如果简单的这样做以后,会存在当x为负数时舍入的问题,比如当x=-1、-2、-3、-5、-6、-7... ...时,((x<<1)+x)>>2会比x*3/4小1,而当x=-4、-8、-12时,((x<<1)+x)>>2才会与x*3/4相等。所以为了解决这个问题,进行修正,设a=(x<<1)+x,修正为(a+(a>>31)&0x3)>>2,这样子就可以啦!如 图24:编写第8个函数ezThreeFourths 所示。
(图24:编写第8个函数ezThreeFourths)
第9个函数,isLessOrEqual。首先从大方向上来看,存在一个或的情况,也就是说,当x等于y时也返回1,所以大框架为:
( ) | ( !(x^y) )
这样子解决了或的问题,下一步就应该要解决isLess的问题。怎么解决这个问题呢?第一反应是通过计算x-y=x+~y+1,根据结果的符号位来判断是否小于。但问题在于,虽然一般情况下这种方法是对的,但如果是一个很小的负数减去一个很大的正数,或者一个很大的正数减去一个很小的负数,这时int类型就无法表示相减的结果,甚至无法表示1-0x8000 0000的结果。所以就需要根据int类型能不能放得下相减的结果,来进行分类讨论。那么如何判断int类型能不能放得下相减的结果呢?我们发现,只有当x与y异号时才存在溢出的问题,所以我们可以将x与y是否同号作为分类依据,这样子大框架进一步细分为:
(( (x^~y)&( ) | ( ) ) >>31)&1 | ( !(x^y) )
当x与y同号时,x与~y异号,x^~y的符号位为1,此时x-y的符号位为即为我们想返回的结果,所以直接返回x-y即可,这时大框架即为:
(( (x^~y)&(x+~y+1) | ( ) ) >>31)&1 | ( !(x^y) )
而当x与y异号时,x与~y同号,x^~y的符号位为0,此时选择x&~y,如果x符号位为0,那么x一定大于y,最终应该返回0,而x&~y恰好就是0;如果x符号位为1,那么x一定小于y,最终应该返回1,而x&~y恰好就是1。利用这个性质,最终的大框架即可完成:
(( (x^~y)&(x+~y+1) | ( x&~y ) ) >>31)&1 | ( !(x^y) )
而如果只选择直接看x的符号位是否为1,而不是选择x&~y的话,有可能出现x与y同号并且x-y符号位为0并且x符号位为1,这时(x^~y)&(x+~y+1)符号位为0,x符号位为1,0|1为1,返回1,但我们想要其返回0,这就出现了冲突。为了避免这个冲突,就需要保证当x与y同号,x^~y的符号位为1时,我们选择的表达式一定为0,所以这才选择了x&~y。有些同学会问,那我们最开始的时候,为什么不写成( !!(x^y)&() ) | ( !(x^y) ),是不是也是这个道理呢?对。因为当!(x^y)为1,即x==y时,!!(x^y)&()中的()一定为0,所以我们才选择省略了!!(x^y)。最终,如 图25:编写第9个函数isLessOrEqual 所示。
(图25:编写第9个函数isLessOrEqual)
第10个函数,rotateLeft,将x旋转左移n位。比如0x87654321左移4位得到了0x76543218,该怎么得到0x76543218呢?我们发现,其由两部分组成,分别是0x76543210与0x8。0x76543210可直接由x<<4得到,0x8可有x>>28然后与0xF做位与得到。最终4可被n代替,28可被32+~n+1代替,那么如何得到0xF呢?最省运算符的方法是,~0+(1<<n)。所以按照这个思路,我们可写出表达式为"(x<<n)|((x>>(32+~n+1))&(~0+(1<<n)))",最终如 图26:编写第10个函数rotateLeft 所示。
(图26:编写第10个函数rotateLeft)
/*
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
return ~(x&y)&~(~x&~y);
}
/*
* isZero - returns 1 if x == 0, and 0 otherwise
* Examples: isZero(5) = 0, isZero(0) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 2
* Rating: 1
*/
int isZero(int x) {
return !x;
}
/*
* thirdBits - return word with every third bit (starting from the LSB) set to 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 1
*/
int thirdBits(void) {
int t=0x49+(0x49<<9);
return t+(t<<18);
}
/*
* anyOddBit - return 1 if any odd-numbered bit in word set to 1
* Examples anyOddBit(0x5) = 0, anyOddBit(0x7) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 2
*/
int anyOddBit(int x) {
int a=0xAA;
int b=a<<8;
int c=a+b;
int d=c+(c<<16);
return !!(x&d);
}
/*
* isEqual - return 1 if x == y, and 0 otherwise
* Examples: isEqual(5,5) = 1, isEqual(4,5) = 0
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int isEqual(int x, int y) {
return !(x^y);
}
/*
* leastBitPos - return a mask that marks the position of the
* least significant 1 bit. If x == 0, return 0
* Example: leastBitPos(96) = 0x20
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 6
* Rating: 2
*/
int leastBitPos(int x) {
return ((~x)+1)&x;
}
/*
* isPositive - return 1 if x > 0, return 0 otherwise
* Example: isPositive(-1) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 2
*/
int isPositive(int x) {
return !(x>>31|!x);
}
/*
* ezThreeFourths - multiplies by 3/4 rounding toward 0,
* Should exactly duplicate effect of C expression (x*3/4),
* including overflow behavior.
* Examples: ezThreeFourths(11) = 8
* ezThreeFourths(-9) = -6
* ezThreeFourths(1073741824) = -268435456 (overflow)
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 3
*/
int ezThreeFourths(int x) {
int a=(x<<1)+x;
return (a+((a>>31)&0x3))>>2;
}
/*
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
*/
int isLessOrEqual(int x, int y) {
return ((((((x+~y+1)&(x^~y))|(x&~y))>>0x1f))&1)|!(x^y);
}
/*
* rotateLeft - Rotate x to the left by n
* Can assume that 0 <= n <= 31
* Examples: rotateLeft(0x87654321,4) = 0x76543218
* Legal ops: ~ & ^ | + << >> !
* Max ops: 25
* Rating: 3
*/
int rotateLeft(int x, int n) {
return (x<<n)|((x>>(32+~n+1))&(~0+(1<<n)));
}