CSAPP,深入理解计算机系统原书第三版的第二章的课后习题[小白版]

前言

本文是CSAPP,深入理解计算机系统原书第三版的第二章的课后习题,有加自己的一些理解,

资源主要来自于:

CSAPP-3E-Solution深入理解计算机系统(第三版)作业题答案(第二章)练习题:CSAPP Chapter2 Homework(已完结),非常感谢。

2.55 在不同机器运行show_bytes

#include<stdio.h>
typedef unsigned char * byte_pointer;
void show_bytes(byte_pointer start,size_t len){
	size_t i;
	for(i=0;i<len;i++)
		printf(" %2.x",start[i]);
	printf("\n");
}
void show_int(int x){
	show_bytes((byte_pointer)&x,sizeof(int));
}
void show_float(float x){
	show_bytes((byte_pointer)&x,sizeof(float));
}
void show_pointer(void *x){
	show_bytes((byte_pointer)&x,sizeof(void *));
}
void test_show_bytes(int val){
	int ival=val;
	float fval=(float) ival;
	int *pval=&ival;
	show_int(ival);
	show_float(fval);
	show_pointer(pval);
}
int main(){
	test_show_bytes(12345);
    return 0;
}

10#=>16#得到12345=>0x00003039(整型表示),浮点型0x4640E400

window:

39 30 00 00
 00 e4 40 46
 dc f5 7f b7 6b 00 00 00

Linux:

39 30 00 00
 00 e4 40 46
 1c 88 89 e8 fe 7f 00 00

window和linux都是小端法机器

2.56 用不同示例值运行show_bytes

更改test_show_bytes();传入值

2.57 编写show_short,show_long,show_double

#include<stdio.h>
typedef unsigned char * byte_pointer;
void show_bytes(byte_pointer start,size_t len){
	size_t i;
	for(i=0;i<len;i++)
		printf(" %.2x",start[i]);
	printf("\n");
}
void show_short(short x){
	show_bytes((byte_pointer)&x,sizeof(short));
}
void show_long(long x){
	show_bytes((byte_pointer)&x,sizeof(long));
}
void show_double(double x){
	show_bytes((byte_pointer)&x,sizeof(double));
}
void test_show_bytes(int val){
	short sval=(short)val;
	long lval=(long) val;
	double dval=(double) val;
	show_short(sval);
	show_long(lval);
	show_double(dval);
}
int main(){
	int num=12345;
	test_show_bytes(num);
	return 0;
}

windows

39 30
 39 30 00 00
 00 00 00 00 80 1c c8 40

linux同上

2.58 编写过程is_little_endian,在小端法机器运行返回1,大端法返回0,无论机器字长

定义一个int变量,值为0xff,剩下两个16#由0补上,0x00ff。

#include<stdio.h>
#include <assert.h>
typedef unsigned char * byte_pointer;
int is_little_endian(){
	int num=0xff;
	byte_pointer byte_start=(byte_pointer)&num;
	if(byte_start[0]==0xff)//相等说明先返回ff而不是0
		return 1;//小端返回1
	else
		return 0;//大端返回0
}
int main(){
	assert(is_little_endian());
	return 0;
}

2.59 表达式,生成一个字,由x的最低有效位字节和y中剩下的字节组成,对于运算数x=0x89ABCDEF和y=0x76543210,得到0x76543EF

表达式:x&0xFF | y&~0xFF

解释:

x&0xFF,得到x最低有效位,y&~0xFF得到y最低有效位之外的位,两者进行或运算,得到组合结果

#include<stdio.h>
#include<assert.h>

int main(){
	size_t mask=0xff;
	size_t x=0x89ABCDEF;
	size_t y=0x76543210;
	size_t res=(x&mask)|(y&~mask);
	assert(res==0x765432EF);
	return 0;
}

2.60 函数,返回无符号值,从0(最低位)到w/8-1(最高位)编号,参数x的字节i被替换成字节b

函数头:

unsigned replace_byte(unsigned x,int i,unsigned char b)

示例:

replace_bytes(0x12345678,2,0xAB)-->0x12AB5678,第2位为34(78为0,56为1)

replace_bytes(0x12345678,0,0xAB)-->0x12AB56AB

#include<stdio.h>
#include<assert.h>

/*获取x的最高有效位*/
int get_msb(int x){
	int shift_val=(sizeof(int)-1)<<3;//1.当int有4位,(4-1)左移3位相当于*8,得到24,2.当int为8位,(8-1)*8=56
	int xright=x>>shift_val;//算数右移,1. 32-8=24,右移24,只剩前面8字节,即最高有效位的两个16#数字,2. 8*8-56=8
	return xright&0xFF;//除最高有效位外全赋值为0
}
unsigned replace_byte(unsigned x,int i,unsigned char b){
	// 1 byte has 8 bits, << 3 means * 8
	unsigned mask = ((unsigned) 0xFF) << (i << 3);
	/* i<<3即i*8,将位置i*8(4bit表示一个16#,8bit表示两个,即一位),i*8得到在bit上对应i的位置,
	  ((unsigned) 0xFF) << (i << 3)通过 (i << 3),将i对应的位置赋值为全1
	 */
	unsigned pos_byte = ((unsigned) b) << (i << 3);
	//将b移动到对应的位置
	return (x & ~mask) | pos_byte;//x&~mask将位置i上的值赋值为全1,与pos_byte与运算将位置i赋值为b
}
int main(){
	int num=0x12345678;
	printf("%.2x\n",get_msb(num));
	unsigned rep_0 = replace_byte(0x12345678, 0, 0xAB);
	unsigned rep_3 = replace_byte(0x12345678, 3, 0xAB);
	
	assert(rep_0 == 0x123456AB);
	assert(rep_3 == 0xAB345678);
	return 0;
}

2.61 表达式,在下列描述条件下产生1,其他情况得到0,假设x是int类型。

A.x的任何位都等于1

B.x的任何位都等于0

C.x的最低有效位字节中的位都等于1

D.x的最高有效位字节中的位都等于0

要求:不允许使用==!=

tips:

  • ! :代表值取反,对于整形变量,只要不为0,使用 ! 取反都是0,0取反就是1。就像 bool 只有真假一样
  • ~ :位的取反,对每一个二进制位进行取反,0变1,1变0

表达式:

A: !~x

x位取反,若x全1,x为全0,!x得到1;若x存在不等于1的位,x位取反后此位得到1,x不全为0,!x得到0

B: !x

x值取反,若x全0,值取反之后得到1

C: !~(x | ~0xff)

~0xff=ffffff00

x|(~0xff)=ffffff x的最低有效位

~=000000 x的最低有效位按位取反

错误写法!~(x&0xFF),x全1,x&0xff=ff,~(x&0xff)=ffffff00

D:!((x >> ((sizeof(int)-1) << 3)) & 0xff)

shift_val=(sizeof(int)-1) << 3):1.当int有4位,(4-1)左移3位相当于8,得到24,2.当int为8位,(8-1)8=56

xright=x>>shift_val:算数右移,1. 32-8=24,右移24,只剩前面8字节,即最高有效位的两个16#数字,2. 8*8-56=8
xright&0xFF:除最高有效位外全赋值为0

代码

#include <stdio.h>
#include <assert.h>

int A(int x) {
	return !~x;
}

int B(int x) {
	return !x;
}

int C(int x) {
	return A(x | ~0xff);
}

int D(int x) {
	return B((x >> ((sizeof(int)-1) << 3)) & 0xff);
}

int main(int argc, char* argv[]) {
	int all_bit_one = ~0;
	int all_bit_zero = 0;
	
	assert(A(all_bit_one));
	assert(!B(all_bit_one));
	assert(C(all_bit_one));
	assert(!D(all_bit_one));
	
	assert(!A(all_bit_zero));
	assert(B(all_bit_zero));
	assert(!C(all_bit_zero));
	assert(D(all_bit_zero));
	
	// test magic number 0x1234ff
	assert(!A(0x1234ff));
	assert(!B(0x1234ff));
	assert(C(0x1234ff));
	assert(D(0x1234ff));
	
	// test magic number 0x1234
	assert(!A(0x1234));
	assert(!B(0x1234));
	assert(!C(0x1234));
	assert(D(0x1234));
	
	return 0;
}

2.62 函数,对int类型的数使用算数位移的机器上运行此函数生成1,其他情况0,在任意字长都可以运行。

函数头int_shifts_are_arithmetic()

变量x设置为全1,num = ~0或者-1,通过表达式 !(num ^ (num >> 1))得到结果。

num位为全1,>>1 ,算数位移后仍然是全1,通过抑或运算,如果为算数位移,抑或后得到全0,否则不全0,再进行值取反。

//#include <stdio.h>
//#include <assert.h>
//
//   
//int int_shifts_are_arithmetic(){
//	int x=~0;//全1
//	int shift_val=(sizeof(int)-1)<<3;//1.当int有4位,(4-1)左移3位相当于*8,得到24,2.当int为8位,(8-1)*8=56
//	int x_shifted=x>>shift_val;
//	if(x_shifted==x)
//		return 1;
//	else
//		return 0;
//}
//
//int main(int argc, char* argv[]) {
//	
//	printf("%d\n",int_shifts_are_arithmetic());
//	return 0;
//}


#include <stdio.h>
#include <assert.h>

int int_shifts_are_arithemetic() {
	int num = ~0;//-1;
	return !(num ^ (num >> 1));
}

int main(int argc, char* argv[]) {
	assert(int_shifts_are_arithemetic());
	return 0;
}

2.63 将代码补充完整,srl使用算数右移(由值xsra给出)来完成逻辑右移,sra(由值xsrl给出)使用逻辑右移完成算数右移

其他操作不包括右移或者除法,可以通过计算8*sizeof(int)来确定数据类型int中的位数w,位移量k的取值范围为0~w-1。

unsigned srl(unsigned x,int k){
    unsigned xsra=(int)x>>k;
    ...
}
unsigned sra(unsigned x,int k){
    unsigned xsrl=(unsigned)x>>k;
    ...
}

补充后:

#include <stdio.h>
#include <assert.h>

//通过算数位移完成逻辑位移
unsigned srl(unsigned x,int k){
	unsigned xsra=(int)x>>k;//int右移为算数位移
	int w=sizeof(int)<<3;//计算int位数
	int mask=(int)-1<<(w-k);//-1即全1位,左移将右边的w-k位设置为0,左边k位仍然是全1
	return xsra&~mask;//~mask得到左k位为0,右边全1
}
//通过逻辑位移完成算数位移
unsigned sra(unsigned x,int k){
	unsigned xsrl=(unsigned)x>>k;//转为unsigned,进行逻辑位移
	int w=sizeof(int)<<3;//计算int位数
	int mask=(int)-1<<(w-k);
	int m = 1 << (w - 1);//第一位是1,其余0
	//当x的第一位为1时,让掩码保持不变,否则为0。
	mask&= ! (x & m) - 1;//x&m=1则x第一位是1,否则0;值取反=1(第一位0),0(第一位1);再-1=-1(第一位1),0(第一位0);mask&-1不变,&0为0
	return xsrl | mask;
}
int main() {
	unsigned test_unsigned = 0x12345678;
	int test_int = 0x12345678;
	
	assert(srl(test_unsigned, 4) == test_unsigned >> 4);
	assert(sra(test_int, 4) == test_int >> 4);
	
	test_unsigned = 0x87654321;
	test_int = 0x87654321;
	
	assert (srl (test_unsigned, 4) == test_unsigned >> 4);
	assert (sra (test_int, 4) == test_int >> 4);
	
	return 0;
}

2.64 实现any_odd_one函数

当x有任意奇数位等于1时返回1,否则返回0,假设w=32

表达式:!!(0xAAAAAAAA & x)

因为A=1010,0xA...A &x即和x中奇数位进行与运算;接下来两次值取反:

  • 第一次值取反,若奇数位存在1,非全0值取反会得到0,第二次值取反得到1
  • 第一次值取反,若奇数位不存在1,全0值取反得到1,第二次值取反得到0;
#include <stdio.h>
#include <assert.h>

int any_odd_one(unsigned x) {
	//A=1010,0xA...A &x即和x中奇数位进行与运算,第一次值取反,若奇数位存在1,非全0值取反会得到0,第二次值取反得到1;
	//第一次值取反,若奇数位不存在1,全0值取反得到1,第二次值取反得到0;
	return !!(0xAAAAAAAA & x);
}

int main(int argc, char* argv[]) {
	assert(any_odd_one(0x2));
	assert(!any_odd_one(0x4));
	return 0;
}

2.65 实现odd_ones函数

当x包含奇数个1时返回1;否则返回0,假设w=32,代码最多包含12个算术运算、位运算和逻辑运算。

思路来自这里,首先需要知道,执行 x ^= x >> L 时,结果的最后L位将具有与x的原始值的最后2*L位的奇偶性相同。
请先思考为什么...


因为1 ^ 1 = 0,那么当我在x ^= x >> L中使用XOR时,它只是删除了2*i位数的1。这就是为什么最后的L位与x的奇偶性相同。

以8位为例介绍

思路来自这里

八位记为D7~D0:D7D6D5D4D3D2D1D0。

x=D7...D0,则x>>4得到0000D7D6D5D4

  1. x=x>>4即`D7D6D5D4D3D2D1D0`0000D7D6D5D4

    设D4=D0的结果为E0,若E0=1,说明D4和D0共有奇数个1,E0=0说明D4和D0共偶数个1,同样设D5=D1的结果为E0,若E1=1,说明D5和D1共有奇数个1,E1=0说明D5和D1共偶数个1。同理得到D5D2=E2,D7D3=E3。

    抑或运算结束后,x=XXXXE3E2E1E0(X后面会被去掉)

  2. x=x>>2即`XXXXE3E2E1E0`00XXXXE3E2

    E0E2=F0,若F0=0,说明E0和E2共有奇数个1,而E0和E2共有奇数个1等价于D4D0D5D2共奇数个1,若F0=0,等价于D4D0D5D2共偶数个1。同理得到E1E3=F1。

    抑或运算结束后,x=XXXXXXF1F0

  3. x=x>>1即F1F0

    G1=F1^F0,若G1=1,说明F1F0共奇数个1,等价于E1E3E0E2共奇数个1,等价于D7D6D5D4D3D2D1D0共奇数个1。若G1=0,等价于D7D6D5D4D3D2D1D0共偶数个1。

    抑或运算结束后,x=XXXXXXXG1

  4. x&=0x1,即G1&0x1

    若G1=1,&1后得到1,而G1=1等价于D7D6D5D4D3D2D1D0共奇数个1,前面的7个X因为&0得到0;若G1=0,&1后得到0,而G1=0等价于D7D6D5D4D3D2D1D0共偶数个1,前面的7个X同样因为&0得到0。

思路与代码

通过连续折半进行抑或运算,保留x的奇偶性,直到用最低位的1位来表示x的奇偶性,再和0x1进行与运算,将前面多余的高位去掉。

/*当x包含奇数个1时返回1;否则返回0,假设w=32,代码最多包含12个算术运算、位运算和逻辑运算。*/
#include <stdio.h>
#include <assert.h>


int odd_ones(unsigned x) {
	x ^= x >> 16;/*通过连续XOR值的一半来XOR所有的比特,直到只剩下一个比特。
直到只剩下一个单一的位。*/
	x ^= x >> 8;
	x ^= x >> 4;
	x ^= x >> 2;
	x ^= x >> 1;
	x &= 0x1;/* 仅保留最低位的掩码。 */
	return x;
}

int main(int argc, char* argv[]) {
	assert(odd_ones(0x10101011));
	assert(!odd_ones(0x01010101));
	return 0;
}

2.66 实现leftmost_one

生成表示 x 中最左边 1 的掩码,假设w=32,例如,0xFF00->0x8000,0x6600->0x4000。

如果x=0,返回0。最多包含15个算术运算、位运算和逻辑运算。

提示:先将x转换成形如[0...011...1]的位向量。

思路

来自这里,和

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答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、付费专栏及课程。

余额充值