《深入理解计算机系统》第二章
- 练习题
- 练习题2.1 完成下面的数字转换:
- 练习题2.2 填写下表中的空白项,给出2的不同次幂的二进制和十六进制表示:
- 练习题2.3 一个字节可以用两个十六进制数字表示。填写下表中缺失的项,给出不同字节模式的十进制、二进制和十六进制值:
- 练习题2.4 不将数字转换为十进制或者二进制,试着解答下面的算术题,答案用十六进制表示。
- 练习题2.5 思考下面对 show_bytes的三次调用:
- 练习题2.6 使用show_int 和show_float,我们确定整数3510593的十六进制表示为0x00359141,二浮点数3510593.0的十六进制表示为0x4A564504。
- 练习题2.7 下面对show_bytes的调用将输出什么结果?
- 练习题2.8 填写下表,给出位向量的布尔运算的求值结果。
- 练习题2.9 通过混合三种不同颜色的光(红绿蓝),计算机可以在视屏屏幕或者液晶显示屏上产生彩色的画面。设想一种简单的方法,使用三种不同颜色的光,每种光都能打开或关闭,投射到玻璃屏幕上。
- 练习题 2.10 对于任一位向量a,有a^a = 0.应用这一属性,考虑下面的程序:
- 练习题2.11 在练习题2.10中的inplace_swap函数的基础上,你决定写一段代码,实现将一个数组中的元素头尾两端依次对调。你写出下面这个函数:
- 练习题2.12 对于下面的值,写出变量x的C语言表达式。你的代码应该对任何字长 w ≥ 8 w\ge8 w≥8都能工作。我们给出了当x = 0x87654321以及 w = 32 w = 32 w=32时表达式求值的结果,仅供参考。
- 课后习题
- 2.61 写一个C表达式,在下列描述的条件下产生1,而在其他情况下得到0,假设x是int类型
- A. x的任何位都等于1
- B. x的任何位都等于0
- C. x的最低有效字节中的位都等于1
- D. x的最低有效字节中的位都等于0
- 2.62
练习题
练习题2.1 完成下面的数字转换:
A. 将0x39A7F8转换为二进制
0011 1001 1010 0111 1111 1000
3 9 A 7 F 8
做法就是把每个数字/字母按顺序写(转为4位二进制)即可
B. 将二进制1100100101111011
转换为十六进制
按照上面的反向操作,四位一组转为对应十六进制即可
0xC97B
C.将0xD5E4C
转换为二进制
11010101111001001100
分组来看就是
1101(D) 0101(5) 1110(E) 0100(4) 1100(C)
D.将二进制1001101110011110110101
这个时候分组会发现:如果从前往后分就会出现最后一组只有两个的情况(这是不对的)
应该从后往前分
10 0110 1110 0111 1011 0101
那么最高位的一组只有两位也可以补0去看,也可以不补
0010 0110 1110 0111 1011 0101
答案就是
0x26E7B5
练习题2.2 填写下表中的空白项,给出2的不同次幂的二进制和十六进制表示:
n | 2 n ( 十进制 ) 2^n(十进制) 2n(十进制) | 2 n ( 十六进制 ) 2^n(十六进制) 2n(十六进制) |
---|---|---|
9 | 512 | 0x200 |
19 | ||
16 384 | ||
0x10000 | ||
17 | ||
32 | ||
0x80 |
第二行: n = 19
十进制: 2 19 = 524288 2^{19} = 524288 219=524288
十六进制可以由十进制转过去,也可以通过以下方法计算
因为 16 = 2 4 16 = 2^4 16=24
2 19 = 2 4 4 × 2 3 = 1 6 4 × 8 2^{19} = 2^{4^4} \times 2^3 = 16^4 \times 8 219=244×23=164×8
所以十六进制表示:0x80000
= 8 ∗ 1 6 4 + 0 ∗ 1 6 3 + 0 ∗ 1 6 2 + 0 ∗ 1 6 1 + 0 ∗ 1 6 0 8*16^4 + 0*16^3 + 0*16^2 + 0*16^1 + 0*16^0 8∗164+0∗163+0∗162+0∗161+0∗160
第三行:十进制 16384
快速做法: 2 10 = 1024 2^{10} = 1024 210=1024
用16384 去除以 1024 得到 16
而16是 2 4 2^4 24
所以 16384 = 1024 ∗ 16 = 2 10 ∗ 2 4 = 2 14 16384 = 1024 * 16 = 2^{10} * 2^4 = 2^{14} 16384=1024∗16=210∗24=214
所以n = 14
再用上面(第二行)的方法
2 14 = 2 4 3 ∗ 2 2 = 4 ∗ 1 6 3 2^{14} = 2^{4^3} * 2^2 = 4 * 16^3 214=243∗22=4∗163
十六进制就是0x4000
第四行: 十六进制: 0x10000
读位数发现0x10000
= 1 6 4 16^4 164
1 6 4 = 2 4 4 = 2 16 16^4 = 2^{4^4} = 2^{16} 164=244=216
所以n = 16
转十进制就没啥技巧了计算 1 6 4 16^4 164或者 2 16 2^{16} 216即可
第五到七行重复二到四行的方法即可得到答案
完整答案:
n | 2 n ( 十进制 ) 2^n(十进制) 2n(十进制) | 2 n ( 十六进制 ) 2^n(十六进制) 2n(十六进制) |
---|---|---|
9 | 512 | 0x200 |
19 | 524 288 | 0x80000 |
14 | 16 384 | 0x4000 |
16 | 65 536 | 0x10000 |
17 | 131 072 | 0x20000 |
5 | 32 | 0x20 |
7 | 128 | 0x80 |
练习题2.3 一个字节可以用两个十六进制数字表示。填写下表中缺失的项,给出不同字节模式的十进制、二进制和十六进制值:
十进制 | 二进制 | 十六进制 |
---|---|---|
0 | 0000 0000 | 0x00 |
167 | ||
62 | ||
188 | ||
0011 0111 | ||
1000 1000 | ||
1111 0011 | ||
0x52 | ||
0xAC | ||
0xE7 |
这题的技巧就是遇到十进制和二进制首先转为十六进制
- 二进制转十六进制更快 , 十六进制转十进制乘法运算更少
- 十进制转十六进制比转二进制快(况且还是只有两个十六进制数字)
第二行 十进制: 167
通过计算或者直觉可得 167 = 16 ∗ 10 + 7 167 = 16*10 + 7 167=16∗10+7
所以十六进制为0xA7
二进制为1010 01111
第五行 二进制 0011 0111
转为十六进制为0x37
转为十进制为 3 ∗ 1 6 1 + 7 = 55 3*16^1 + 7 = 55 3∗161+7=55
第八行 十六进制0x52
转为十进制: 5 ∗ 16 + 2 = 82 5*16+2 = 82 5∗16+2=82
转为二进制:0101 0010
完整答案:
十进制 | 二进制 | 十六进制 |
---|---|---|
0 | 0000 0000 | 0x00 |
167 | 1010 01111 | 0xA7 |
62 | 0011 1110 | 0x3E |
188 | 1011 1100 | 0xBC |
55 | 0011 0111 | 0x37 |
136 | 1000 1000 | 0x88 |
243 | 1111 0011 | 0xF3 |
82 | 0101 0010 | 0x52 |
172 | 1010 1100 | 0xAC |
231 | 1110 0111 | 0xE7 |
练习题2.4 不将数字转换为十进制或者二进制,试着解答下面的算术题,答案用十六进制表示。
A. 0x503c + 0x8 =
对齐位,做第一次加法c + 8 = 20
, 保留 20 - 16 = 4
并且进一位
所以答案为0x5044
B. 0x503c - 0x40 =
最低位做减法,个位为c-0 = c
次低位做减法,十位为3 - 4 = -1
向高位借一位16+3 - 4 = 15 = F
接着继续借位即可
所以答案为0x4FFc
C.0x503c + 64 =0x507c
将64
转为十六进制0x40
然后做加法即可
D.0x50ea-0x503c=0xAE
练习题2.5 思考下面对 show_bytes的三次调用:
int val = 0x87654321;
byte_pointer valp = (byte_pointer) & val;
show_bytes(valp,1);/*A.*/
show_bytes(valp,2);/*B.*/
show_bytes(valp,3);/*C.*/
指出在小端法机器和大端法机器上,每次调用的输出值。
A.小端法: 大端法:
B.小端法: 大端法:
C.小端法: 大端法:
show_bytes()
函数如下(在书的上面)
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");
}
小端法:
首先明白小端其实是最低有效字节在最前面的方式
低地址0x21 0x43 0x65 0x87
高地址
大端法:
低地址0x87 0x65 0x43 0x21
高地址
然后看第二个参数传的是打印的长度
所以
A.大端法:0x21
A.小端法:0x87
B.大端法:0x21 0x43
B.小端法:0x87 0x65
C.大端法:0x21 0x43 0x65
C.小端法:0x87 0x65 0x43
注:答案没有空格(空格是为了看的方便)
练习题2.6 使用show_int 和show_float,我们确定整数3510593的十六进制表示为0x00359141,二浮点数3510593.0的十六进制表示为0x4A564504。
A.写出这两个十六进制值的二进制表示。
B.移动这两个二进制串的相对位置,使得它们相匹配的位数最多。有多少位相匹配呢?
C.串中的什么部分不相匹配?
A.
0x00359141
转为二进制:0011 0101 1001 1001 0001 0100 0001
0x4A564504
转为二进制:0100 1010 0101 0110 0100 0101 0000 0100
B.
. 0011010110011001000101000001
01001010010101100100010100000100
23位相匹配
C. ???
练习题2.7 下面对show_bytes的调用将输出什么结果?
const char *s = "abcdef";
show_bytes((byte_pointer)s,strlen(s));
答案:依次打印'a'-''f
的ASCLL码即可
0x61 0x62 0x63 0x64 0x65 0x66
练习题2.8 填写下表,给出位向量的布尔运算的求值结果。
运算 | 结果 |
---|---|
a a a | [ 01101001 ] [01101001] [01101001] |
b b b | [ 01010101 ] [01010101] [01010101] |
~a | |
~b | |
a&b | |
a ∣ b a|b a∣b | |
a^b |
记住
按位且(&)是全1则1
按位或(|)是有1则1
按位异或(^)是有异(不同)则1
按位取反是1/0互换
完整答案:
运算 | 结果 |
---|---|
a a a | [ 01101001 ] [01101001] [01101001] |
b b b | [ 01010101 ] [01010101] [01010101] |
~a | [ 10010110 ] [10010110] [10010110] |
~b | [ 10101010 ] [10101010] [10101010] |
a&b | [ 01000001 ] [01000001] [01000001] |
a ∣ b a|b a∣b | [ 01111101 ] [01111101] [01111101] |
a^b | [ 00111100 ] [00111100] [00111100] |
练习题2.9 通过混合三种不同颜色的光(红绿蓝),计算机可以在视屏屏幕或者液晶显示屏上产生彩色的画面。设想一种简单的方法,使用三种不同颜色的光,每种光都能打开或关闭,投射到玻璃屏幕上。
那么基于光源RGB的开关,我们就能创建8种不同的颜色
R | G | B | 颜色 |
---|---|---|---|
0 | 0 | 0 | 黑色 |
0 | 0 | 1 | 蓝色 |
0 | 1 | 0 | 绿色 |
0 | 1 | 1 | 蓝绿色 |
1 | 0 | 0 | 红色 |
1 | 0 | 1 | 红紫色 |
1 | 1 | 0 | 黄色 |
1 | 1 | 1 | 白色 |
这些颜色中的每一种都能用一个长度为3的位向量来表示,我们可以对它们进行布尔运算。
A.一种颜色的补是通过关掉打开的光源,且打开关闭的光源而形成的那么上面列出的八种颜色每一种的补是什么?
颜色 | 补 颜色 |
---|---|
黑色 | 白色 |
蓝色 | 黄色 |
绿色 | 红紫色 |
蓝绿色 | 红色 |
红色 | 蓝绿色 |
红紫色 | 绿色 |
黄色 | 蓝色 |
白色 | 黑色 |
B.描述下列颜色应用布尔运算的结果:
蓝色 | 绿色 = 蓝绿色
黄色 & 蓝绿色 = 绿色
红色 ^ 红紫色 = 蓝色
练习题 2.10 对于任一位向量a,有a^a = 0.应用这一属性,考虑下面的程序:
void inplace_swap(int *x,int *y){*y = *x^*y;/*Step 1*/*x = *x^*y;/*Step 2*/*y = *x^*y;/*Step 3*/
}
步骤 | *x | *y |
---|---|---|
初始 | a | b |
第1步 | a | a^b |
第2步 | a ^ (a ^ b) = (a ^ a) ^ b = b | a^b |
第1步 | b | b ^ (a ^ b) = (b ^ b) ^ a = a |
练习题2.11 在练习题2.10中的inplace_swap函数的基础上,你决定写一段代码,实现将一个数组中的元素头尾两端依次对调。你写出下面这个函数:
void reverse_array(int a[],int cnt){int first,last;for(first = 0,last = cnt - 1;first <= last;first++,last--)inplace_swap(&a[first],&a[last])
}
A.对于一个长度为奇数的数组,长度cnt = 2k+1,函数 reverse_array最后一次循环中,变量first和last的值分别是什么?
f i r s t = l a s t = ⌊ c n t 2 ⌋ + 1 = ⌈ c n t 2 ⌉ first = last = \lfloor \dfrac{cnt}{2} \rfloor + 1 = \lceil \dfrac{cnt}{2} \rceil first=last=⌊2cnt⌋+1=⌈2cnt⌉
B.为什么这时调用函数inpalce_swap会将数组元素设置为0
设 b = a [ ⌈ c n t 2 ⌉ ] b = a[\lceil \dfrac{cnt}{2} \rceil] b=a[⌈2cnt⌉]
步骤 | *x | *y |
---|---|---|
初始 | b | b |
第1步 | 0 | b^b = 0 |
第2步 | 0 | 0 |
第1步 | 0 | 0 |
在第一步自己和自己异或变成0了还赋值给了自己
C.对reverse_arry的代码做哪些简单改动就能消除这个问题?
将first<=last
改为first < last
使得不会出现自己对自己交换从而避免了自己和自己异或并赋值给自己
练习题2.12 对于下面的值,写出变量x的C语言表达式。你的代码应该对任何字长 w ≥ 8 w\ge8 w≥8都能工作。我们给出了当x = 0x87654321以及 w = 32 w = 32 w=32时表达式求值的结果,仅供参考。
A.x的最低有效字节,其他位均置为0.[0x00000021]
x & 0xFF
B.除了x的最有效字节外,其他位都取补,最低有效字节保持不变。[0x789ABC21]
(~(x&~0xFF)|)(x&0xFF)
C.x的最低有效字节设置成全1,其他字节都保持不变
x|0xFF
课后习题
2.61 写一个C表达式,在下列描述的条件下产生1,而在其他情况下得到0,假设x是int类型
A. x的任何位都等于1
解决方案1:
INT_MAX的任何位都等于1 , 将其与x进行按位且后如果x有一位不等于1那么结果为0
bool chapter2_261A(int x){return x & (INT_MAX);
}
解决方案2:
x的任何位都等于1 , 那么将x进行取反后每一位都等于0,相当于 0
bool chapter2_261A(int x){return !~x;
}
C表达式:
!~x
or x & (INT_MAX)
B. x的任何位都等于0
解决方案:
x的任何位都等于0 , 那么x就是0
bool chapter2_261B(int x){return !x;
}
C表达式:
!x
C. x的最低有效字节中的位都等于1
注意这里说的是有效字节
一般一个字节是8位,一个字节的位都等于1 , 在二进制中相当于1111 1111
,在十六进制中相当于0xFF
解决方案:
先提取出最低有效字节,接着判断是否为全1(可以利用2.61A的方法)
bool chapter2_261C(int x){return chapter2_261A((x & 0xFF) | ~0xFF);
}
可以化简为直接将除了最低为有效字节以外的字节都替换为全1,然后利用2.61A的方法
bool chapter2_261C(int x){return chapter2_261A(x | ~0xFF);
}
C表达式:
!~(x | ~0xFF)
D. x的最低有效字节中的位都等于0
解决方案:
先提取出最高有效字节,接着判断是否为全0(可以利用2.61B的方法和书上的get_msb方法)
int get_msb(int x){int shift_val = (sizeof(int)-1) << 3;int xright = x >> shift_val;return xright & 0xFF;
}
bool chapter2_261D(int x){return chapter2_261B(get_msb(x));
}
这里顺便讲解一下如何提取最高有效字节:
sizeof(int)
获取int的字节数,然后将字节数 - 1,再乘上 8 (1 << 3)也就2的3次方,原因是一个字节是8位。
接着 x >> shift_val
把x右移shift_val
位,实则是将最高位移到最低位的位置
最后将其与0xFF
进行按位且,相当于截取最低位,其余位归0。
最后就能得到最高位有效字节
C表达式:
!((x >> (sizeof(int)-1) << 3) & 0xFF )
2.62
算数右移(Arithmetic Right Shift):
保留符号位(最高位),即对于负数,右移时会填充 1;对于正数,右移时填充 0。
适用于有符号整数,保持数值的符号。
逻辑右移(Logical Right Shift):
不考虑符号位,始终在高位填充 0。
适用于无符号整数,结果不依赖于原数的符号。
更多更新请看GITHUB仓库