CSAPP(一):Data Lab笔记
前言:
《CS:APP》又名深入理解计算机系统,被称为计算机学习的圣经,假期总算有时间进行一个习的学,在阅读书籍,观看CMU网课的同时,也顺便完成了部分lab,不得不说lab很有挑战,对于相应章节知识的学习和理解具有很棒的作用,本篇为第二章内容的lab: DATA LAB的学习笔记,下附相关学习资料。
lab学习官网:CS:APP3e, Bryant and O’Hallaron (cmu.edu)
网课地址:【精校中英字幕】2015 CMU 15-213 CSAPP 深入理解计算机系统 课程视频_哔哩哔哩_bilibili
参考资源和博客:《深入理解计算机系统》第三版的实验文件、解答与笔记 (github.com)
Data Lab笔记
本lab内容主要包括位运算,补码运算,浮点数等相关内容,完成lab需要实现函数功能,同时会限制使用op运算符的个数,因此增加了完成的难度,所有的实现都基于32位系统。
所有的函数实现都在bits.c
中完成,可以使用./btest
对实现函数的正确性进行检验。使用./dlc
对完成函数的语法正确性和使用操作符的限制进行检验,使用./driver.pl
对以上两项检验进行合并。
本次lab实现的函数为2017年版本,最新的版本为2019年版本,对很多实现函数要求进行了更新。
1.位操作
1.bitAnd
1 |
|
题目要求只使用~
和|
完成&
,参考德摩根定律即可实现
2.getByte
1 |
|
本题是从 x 中提取出第 i 个字节(i=0,1,2,3),方法就是将那个字节移位至最低位,然后用屏蔽码 0xff
提取就可以了
3.logicalShift
1 |
|
本题需要实现逻辑右移,本身c语言自带的>>
指的是算术右移,因此可以在算术右移的基础上对其进行改进。
本题做法的核心思路是构造一个掩码,掩码可以对右移后的数据进行更新使其满足题意。例如右移4位,我们构造的掩码即为0x0fffffff
,为此我们可以先构造一个0x7fffffff
,因为其符号位为0,将其算术右移对应的n位后就可以满足上述的条件,但由于其移动n位的掩码正好与我们需要的差一位,因此补充一个(1<<(31-n))的|
即可构造最终的掩码。
4.bitCount
1 |
|
本题的要求是统计32位int中1的个数,可以说难度是前5道题中难度最大的,由于不能使用循环,且有操作符个数限制,因此也不能使用暴力的方法,最终我参考了算法-求二进制数中1的个数 - 翰墨小生 - 博客园 (cnblogs.com)中的平行算法。
这里构造了五个常数,分别是 0x55555555,0x33333333,0x0f0f0f0f,0x00ff00ff,0x0000ffff
,就是分别间隔了1个0,2个0,4个0,8个0和16个0,利用这五个常数就能依次计算出五个值,第一个值每两位的值为 x 的对应的两个位的和(即这两位中 1
的数目),第二个值每四位是第一个值对应的四位中两个两位的和(即原 x 中 1
的数目),依次类推最后一个值就是结果了;
5.bang
1 |
|
本题要求使用不使用!来实现!。我在实现时发现了一个规律,对于x和x-1只有两种情况两个数的第31位是相反的,一是x=0,x-1=0xffffffff
,二是x=0x80000000,x-1=0x7fffffff
,因此利用异或把这种情况再特判要求x的31位为0即可满足条件。
另一种实现是使用了折叠法,把32个位上的1通过折叠的方式聚合到0位上,即只要有一个位为1,最终得到的就是0x1,否则为0,之后再对其进行处理,该方法更加巧妙。
2.补码
6.tmin
1 |
|
最高位为1,其他位为0为最小值。
7.fitsBits
1 |
|
本题为判断用n位能否用补码的形式表示一个x的数字,本题的解法是分情况讨论,分别为x>=0和x<0两种情况,将x向右移n-1位,判断其是否为0(负数的时候为-1),得到最终结果。
8.divpwr2
1 |
|
这题计算 x/(2^n) ,注意不能直接右移,直接右移是向下舍入的,题目要求是向零舍入,也就是正数向下舍入,负数向上舍入,这里参照 CS:APP 书上的做法,给负数加上一个偏正的因子 (0x1<<n)+~0)
,判断负数直接看符号位。
9.negate
1 |
|
利用-x=~x+1
10.isPositive
1 |
|
判断是否x>0,结合符号位,特判x=0的情况即可。
11.isLessOrEqual
1 |
|
对于x<=y,可以分几种情况讨论。
x<0,y>0时显然成立
x<0,y<0,x-y<=0时成立,且x-y不会出现溢出
x>0,y>0,x-y<=0时成立,且x-y不会出现溢出
因此分三种情况讨论结合即可,x1,y1即是对正负性的判断
12.ilog2
1 |
|
上方的flag1-5得到的是x的最高位对应的2进制表示中是否包含16,8,4,2,1的底数,因为其对数可表示成16*a+8*b+4*c+2*d+e
的形式,最终拼接起来即为最终结果,注意的是每一步对x进行了条件判断的赋值。
3.浮点数
以下三题关于浮点数,可以自由使用操作符和分支语句
13.float_neg
1 |
|
本题计算-f,即参数的负数,注意特判NaN的情况。
14.float_i2f
1 |
|
本题为将整型数转化为浮点数格式,可以说是本lab里难度最大的题目,具有很多坑点,解读参考博客的解法。
整体思路就是依次计算符号位,阶码值和小数字段,符号位可以直接移位提取,阶码值就是除了符号位外最高位的位数减 1 再加上偏差 127,小数字段可以移位(负数可以化为正数操作)获得,但这问题没这么简单,有很多坑点:
特殊值 0 化为浮点数后是非规格化的,单独考虑;
特殊值 0x80000000 是 2 的整数倍,小数部分用移位的话因为舍入问题会溢出,单独考虑;
要仔细考虑移位过程,左移还是右移能得到 23 位的小数部分,这里实际上是需要进行分类讨论,左移直接执行即可,右移由于涉及到舍入,所以需要进行一些判断;
注意舍入问题,这里需要仔仔细细地考虑清楚,**默认使用向偶数舍入,就是舍去部分小于中间值舍弃,大于中间值进位,为中间值如 100 就向偶数舍入:就是看前一位,进位或舍弃总使得前一位为 0。 **舍入的知识可以参考(29条消息) 浮点数向偶数舍入的问题_南京大学的CS渣-CSDN博客_向偶数舍入
最后就是操作数目限制在 30 位,这个需要对写完的代码进行一步步精简,我最终的实现正好是30个操作符。
15.float_twice
1 |
|
本题为求参数的2倍,对于无穷大和NAN的情况直接返回,其他情况需要分规格化和非规格化两种情况讨论。
对于规格化的情况,在阶码值上+1,但是对于溢出的时候需要返回无穷大
对于非规格化情况,将除符号位的数字左移一位即可,因为这时阶码值为 0 ,两倍就相当于小数字段左移一位,不用担心溢出的情况,溢出时阶码值加 1,小数字段左移一位,相当于整体左移了。(这里可以感叹一下浮点数编码设计的巧妙!)
总结
本lab虽然面向的知识比较基础,但是难度并不小,需要在写码的过程中,不断琢磨各种精妙和可靠的实现(例如使用|和&来实现分类讨论),花费了数天的时间完成(包括搭建和熟悉环境)。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!