CSAPP习题思考(位操作)

CSAPP习题思考(位操作) 

现在发现写技术方面的文章真是不容易,不像写随感文,随便热血一下两三个小时就出来了。这篇文章至少用了5、6个小时,但依然感觉没写到位,很多想说的却写不出来。想和说(写)是两种境界,所以每次看pongba洋洋散散五六千字,看着不累而且有趣,其背后的心血和深厚沉淀都不是一时半会儿练就的。

记得以前哪个牛人说过:“如何把一个问题解释给一个毫无技术背景但同样很聪明的人听,让别人理解,这才代表自己真正理解了问题。”所以Joel把写作列为优秀程序员的必备素养之一。写作的过程就像和心灵对话,不断在找自己的茬(各种论证的弱点、表述的清晰性、思维过程的总结等等),然后慢慢提高。

言归正传,最近在做《Computer Systems: A Programmer’s Perspective》(CSAPP)的习题,有些题目做得还是挺吐血的,但题目总体颇具思考价值,对增加知识的理解很有帮助。举一些有意思的,主题是位操作。

1) 比较两数是否相等

可用操作:! ~ & ^ | + << >>

最多5步

可以想到判断两数相等就是判断两数的每个位是否相等,所以想个方法来检测每个位是关键。又想到位操作中异或操作其实就是判断两个位是否相等的,即0^0=0, 0^1=1, 1^0=1, 1^1=0。于是只要将两个数异或一下,然后如果为0则是相等,反之则不相等。最后要把结果颠倒下,因为函数要求返回为1是相等,0则不相等。于是得到

 
 
int isEqual(int x, int y) 
{
	return !(x ^ y);
}

2) 逻辑右移n位

可用操作:~ & ^ | + << >>

最多16步

可以想到右移有两种情况:1)负数,右移前n位都为1。2)正数,右移前n位都为0。对于负数如何把前n位置为0就成了问题所在。可以想到如果有一个数前n位为0,后32-n位为1,再把它和移位后的x与一下就是结果了。

于是,我想到做这样一个掩码让它的符号位为1,再右移n位,于是得到一个前n位为1,而后32-n位为0的数,再把这个数取反,就能满足条件了。

 
 
int logicalShift(int x, int n) 
{
	int mask = ~((1 << 31) >> n);   int mask = ~((1 << 31) >> (n -1));
	return (x >> n) & mask;
}

3)如果数中含有1的个数为奇数则返回1,否则返回0

事例:bitParity(5)=0,bitParity(7)=1

可用操作:! ~ & ^ + << >>

最多20步

最容易想到的就是线性的方法了,检查每一个位,为1则累加1。最后检查累加值最低位(奇数的判断)是1,则输出1,否则返回0。但可以看到这样的话需要检查的32步,再加上累加操作的32步,总共64步。大于20步。

经过上述思考,可以发现用线性的方法极有可能是要超出20步的。那么最容易降低复杂度的就是O(logN)的算法。鉴于此,我想到可以用二分的方法试一下。那么,怎么用二分呢?再仔细一看,由于检查的是含有1是否为奇数,并没有要求计算总的数量,所以是否可以用一个操作符来判断奇偶。

怎样的操作数可以判断奇偶,于是想到xor由于xor在两数都不相同时为1,所以多个位作xor操作,得出为1则说明有奇数个1。并且利用二分的思想分解成两个16位->两个8位……->两个1位,如此不断xor。就得出了最后答案。

 
 
int bitParity(int x)
{
	int ret = (x >> 16) ^ x;
	ret = (ret >> 8) ^ ret;
	ret = (ret >> 4) ^ ret;
	ret = (ret >> 2) ^ ret;
	ret = (ret >> 1) ^ ret;
 
 
	return ret & 1;
}

4)完成!x运算,且不使用!运算符

事例:bang(3)=0,bang(0)=1

可用操作:~ & ^ | + << >>

最多12步

首先,这里的!运算其实就是判断这个数是否为0,!0=1其余都为0由于只能使用位操作,所以不能直接x==0来输出。而且最多12步,所以线性的32步的位判断是没出路的。这里最初的想法还是用二分来减少运算。

想到因为0的编码位都为0,所以只要检测出有一位1的就可以得出结果了。那么判断是否有一个位为1就成了解决问题的关键。怎么解决呢?想到了或运算可以保持1的存在。所以似乎觉得可以用或,那么如何结合二分呢?想到了这样一幅图,解决32位中是否有1,其实可以分解成两个16位是否有1,一个16位又可以分解成两个8位……如此不断,就到了两个1位,那最后不就只剩下1位了嘛。

1

这里的核心关键是只要有一位存在1,我们就找出答案了,所以可以使用或操作。于是,想到可以把每个位都互相或一下,答案是1的话就说明有一位存在1。这里就用到了二分思想,把32位分成两部分或一下,再把结果分成8位或一下,最后只剩下一位。可以看出每位的互相或一下和这边的二分之后或其实是等价的。

另外最开始,我设定了一个掩码(mask)把A前部分的不需要的去掉,mask = ~((1 << 31) >> 15),即mask = 0000….11111,A = (x >> 16) & mask,这样A的前16位为0,对B=x & mask。而后发现,这样做会超过步数。仔细分析,其实我们关注的只是那16位,对于不需要的部分其实也不用去掉,于是得到以下代码:

 
 
int bang(int x) 
{
	int ret = (x >> 16) | x;
	ret = (ret >> 8) | ret;
	ret = (ret >> 4) | ret;
	ret = (ret >> 2) | ret;
	ret = (ret >> 1) | ret;
 
 
	return (~ret) & 1;
}

用了正好12步,还是很惊险。

5)判断加法是否溢出

事例:addOK(0x80000000, 0x80000000)=0, addOK(0x80000000, 0x70000000)=1

可用操作:! ~ & ^ | + << >>

最多20步

想破头的一题,开始的对溢出的理解错误。如下:

以下为初始的错误想法:

所谓溢出,我想到的就是溢出到了第33位,那么问题就变成了如何判断相加后第33位是否为1的问题。这里,我再次想到了二分的问题。考虑一个小的问题,比如两个16位的数相加怎么判断溢出,我想只要把它们放到两个32位的数中然后,判断第17位就行了,这个问题很容易。那么现在问题扩大了一倍,而且我手头不能用64位去存储32位数的变量,怎么办呢?所谓二分就是减小问题,不如把32位的数拆成两半,如图:

2

根据加法原理,数从低位加到高位,那么把x和y拆成两部分,判断低16位是很容易的,在把第17位放到高16位(sx2和sy2)的加法运算中,判断是否溢出就可以解决了。

代码如下:

 
 
int addOK(int x, int y) 
{
	int mask = ~((1 << 31) >> 15); //0000....1111
 
 
	int sx = x & mask; //sx1
	int sy = y & mask; //sy1
	int sum = sx + sy;
 
 
	sum >>= 16; // get the 16th bit, sum = 0 or 1, check wethter sx1 + sy1 is overflow(16bit)
 
 
	sx = ((x >> 16) & mask); //sx2
	sy = ((y >> 16) & mask); //sy2
	
	sum += sx + sy;
 
 
	return (sum >> 16);
}
 
 

正解:

而后发现一个简单的反例:0xFF,FF,FF,FF(-1) + 0xFF,FF,FF,FF(-1)=-2,显然没有溢出,但按我的算法是溢出的。错误的想法其实对原码是正确的,但补码的机制不是这样。于是找了好几个例子来看,终于算是搞清了溢出的机制。观察两个正负数相加是肯定不会溢出的,溢出的情况有两种:1)两个负数得正数。2)两个正数得负数。也就是问题可以转换为检查初始两个加数的符号位和运算结果的符号位。

  

于是,我得出两种情况是非法的x和y的符号位1,1得出的符号位0。以及x和y的符号位0,0得出的符号位1。问题是怎么判断,这个时候想念到if的好了,没有if的日子真是非常难过啊,还有那些个||和&&运算符。经过无数的尝试,终于想到一个关键的地方:用两个加数的符号位作为开关。也就是这样一个形式:? & 开关。?代表的应该是加数的符号位和结果的符号位的一些运算。说实话,这道题我是在不断地尝试,至于其背后的机制为什么会想到,现在都感觉像是暴力搜索一样,只是感觉上应该是那样。代码如下:

 
 
int addOK(int x, int y) 
{
	int sign = ~((x ^ y) >> 31); //提取符号位,相同为1,不同为0
	int result = x + y; 
	int xSign = (x >> 31) ^ (result >> 31); //用某一个加数的符号位去xor结果的符号位,为什么只用一个加数而不考虑另一个,因为开关在这里起了一个重要的作用可以忽略这样的情况而保证答案的正确性
	return !(xSign & sign & 1); // 取出最后一位且取反
}
 
 

以上的一些题都用到了二分的方法,虽然二分的具体实现方法不同,但本质的思想。想到用二分的原因有两点。一者,之前看《编程珠玑》时,作者用二分很优美的解决了一些问题。于是二分在印象中比较深刻。二者,这些问题最初的考虑都是用线性方法,也就是最简单的方法,但由于步数的限制必须降低算法复杂度。由O(n)自然会考虑用O(logn)来降低,于是考虑二分就比较自然了。以上只是练习的一部分,还有一些比较类似的就不说了。

再者,列出一些至今还没有想到办法解决的,当然暴力的线性总可以解决,但始终觉得不够优美。

1) 找出最低位1的位置。

事例:leastBitPos(96)=0x20

可用操作:! ~ & ^ | + << >>

最多30步

只想到了暴力方法,完全没头绪。

2) 计算数中为1的位的个数

事例:bitCount(5)=2, bitCount(7)=3

可用操作:! ~ & ^ | + << >>

最多40步

考虑用xor结合and的方法,但细节方面的二分或者其他具体实现还是想不明白。

### 回答1: 深入理解计算机系统(CSAPP)是由Randal E. Bryant和David R. O'Hallaron编写的经典计算机科学教材。该教材通过涵盖计算机体系结构、机器级别表示和程序执行的概念,帮助学生深入理解计算机系统的底层工作原理和运行机制。 深入理解计算机系统的练习题对于学生巩固并应用所学知识非常有帮助。这些练习题涵盖了计算机硬件、操作系统和编译器等多个领域,旨在培养学生解决实际问题和设计高性能软件的能力。 对于深入理解计算机系统的练习题,关键是通过实践进行学习。在解答练习题时,应根据课本提供的相关知识和工具,仔细阅读问题描述,并根据实际需求设计相应的解决方案。 在解答练习题时,需要多角度思考问题。首先,应准确理解题目要求,并设计合适的算法或代码来解决问题。其次,应考虑代码的正确性和效率,以及对系统性能的影响。此外,还要注意处理一些特殊情况和异常情况,避免出现潜在的错误或安全漏洞。 解答练习题的过程中,应注重查阅相关资料和参考优秀的解答。这可以帮助我们扩展对问题的理解,并学习他人的思路和解决方法。同时,还可以通过与同学和老师的讨论,共同探讨问题和学习经验。 总之,通过解答深入理解计算机系统的练习题,可以帮助学生巩固所学知识,同时培养解决实际问题和设计高性能软件的能力。这是一个学以致用的过程,可以加深对计算机系统运行机制和底层工作原理的理解。 ### 回答2: 理解计算机系统(CSAPP)是一本经典的计算机科学教材,通过深入研究计算机系统的各个方面,包括硬件、操作系统和编程环境,对于提高计算机科学专业知识与能力具有很大帮助。 练习题是CSAPP中的重要部分,通过练习题的完成,可以加深对计算机系统的理解,并将理论知识转化为实践能力。练习题的数量、难度逐渐递增,从简单的概念与基础问题到复杂的系统设计与实现。 在解答练习题时,首先需要对题目进行仔细阅读和理解,明确题目的要求和限制条件。然后,可以利用课堂讲解、教材内容、网络资源等进行查阅和学习相应的知识。同时,还可以参考课后习题解答等资料,了解一些常见的解题方法和思路。 在解答练习题时,可以利用计算机系统的工具和环境进行实际测试和验证。例如,可以使用调试器、编译器和模拟器等工具对程序或系统进行分析和测试。这样可以更加深入地理解问题的本质,并找到恰当的解决方法。 另外,解答练习题时还可以与同学、教师和网上社区进行交流和讨论。这样可以互相学习和交流解题思路,共同解决问题。还可以了解不同的解题方法和技巧,提高解题效率和质量。 练习题的解答过程可能会遇到一些困难和挑战,例如理论知识的不足、复杂问题的分析与解决。但是通过不断地思考和实践,相信可以逐渐提高解题能力,更好地理解计算机系统。 总之,深入理解计算机系统(CSAPP)练习题是提高计算机科学专业知识和能力的重要途径。通过仔细阅读和理解题目,查阅相关知识,利用计算机系统工具和环境进行实践,与他人进行交流和讨论,相信可以更好地理解计算机系统的各个方面,并将知识转化为实际能力。 ### 回答3: 《深入理解计算机系统(CSAPP)》是计算机科学领域的经典教材之一,对于深入理解计算机系统的原理、设计和实现起到了极大的帮助。在阅读这本书的过程中,书中的习题也是非常重要的一部分,通过做习题,我们可以更好地理解书中所讲的概念和思想。 CSAPP的习题涵盖了课本中各个章节的内容,从基础的数据表示和处理、程序的机器级表示、优化技术、程序的并发与并行等方面进行了深入探讨。通过解答习题,我们可以对这些知识进行实践应用,巩固自己的理解,并培养自己的解决问题的思维方式。 在解答习题时,我们需要充分理解题目要求和条件,并从知识的角度进行分析。有些习题可能需要进行一些编程实践,我们可以通过编程实现来验证和测试我们的思路和解决方案。在解答问题时,我们还可以查阅一些参考资料和网上资源,充分利用互联网的学习资源。 在解答习题时,我们需要保持积极的思维和态度。可能会遇到一些困难和挑战,但是通过坚持和努力,我们可以克服这些困难,提高我们的解决问题的能力。同时,我们还可以通过与同学或者其他人进行讨论,相互分享解题经验和思路,从而更好地理解问题。 综上所述,通过深入理解计算机系统(CSAPP)的习题,我们可以进一步巩固和深化对计算机系统的理解。掌握这些知识,不仅可以提高我们在计算机领域的能力,还可以为我们未来的学习和职业发展奠定重要的基础。因此,认真对待CSAPP的习题,是我们在学习计算机系统知识中不可或缺的一部分。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值