前言
本文是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)#
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
-
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后面会被去掉)
-
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
-
x=x>>1即F1F0
G1=F1^F0,若G1=1,说明F1F0共奇数个1,等价于E1E3E0E2共奇数个1,等价于D7D6D5D4D3D2D1D0共奇数个1。若G1=0,等价于D7D6D5D4D3D2D1D0共偶数个1。
抑或运算结束后,x=XXXXXXXG1
-
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]的位向量。
思路
来自这里,和