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]的位向量。

思路

来自这里,和

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值