CSAPP:第二章 - 2.2 & 2.3练习题答案
17 ~ 22(略)
23
A
w |
(int)((word << 24) >> 24) |
((int)word << 24) >> 24 |
0x00000076 |
0x00000076 |
0x00000076 |
0x87654321 |
0x00000021 |
0x00000021 |
0x000000c9 |
0x000000c9 |
0xffffffc9 |
0xEDCBA987 |
0x00000087 |
0xffffff87 |
B func1是做高位截断,func2做符号位扩展.
24(略)
25
length-1会有溢出,所以改为 < length
有符号数和无符号数比较也有问题,当length特别大的时候,得不到正确的结果。
所以改为:
unsigned i
for (i = 0; i < length; ++i)
26
经典错误了,无符号数计算结果还是无符号数,产生溢出后变成了一个很大的正数。
int k = ((1u - 2u) > 0);
printf( "%d, %u",k, (1u - 2u));
修改很简单 return strlen(s) > strlen(t);
27
int uadd_ok(unsigned x, unsigned y)
{
unsigned sum = x + y;
return sum > x;
}
28
w=4
x |
x |
-x |
-x |
十六进制 |
十进制 |
十进制 |
十六进制 |
0 |
0 |
0 |
0 |
5 |
5 |
11 |
B |
8 |
8 |
8 |
8 |
D |
13 |
3 |
3 |
F |
15 |
1 |
1 |
29
w = 5, 运算范围为 -16~15
2^5 = 32
x |
y |
x + y |
x + y(t5) |
情况 |
[10100] -12 |
[10001] -15 |
[100101] -27 |
5 |
1 负溢 |
[11000] -8 |
[11000] -8 |
[110000] -16 |
-16 |
2 |
[10111] -9 |
[01000] 8 |
[11111] -1 |
-1 |
2 |
[00010] 2 |
[00101] 5 |
[00111] 7 |
7 |
3 |
[01100] 12 |
[00100] 4 |
[10000] 16 |
-16 |
4 正溢出 |
30
int tadd_ok(int x, int y)
{
int sum = x + y;
if (sum <=0){
return x <= 0 || y <= 0;
} else{
return x > 0 || y > 0;
}
}
31
假设发生了正溢出,那么sum = x + y - 2^w
那么sum - x = (x + y - 2^w) - x = y - 2^w ==> (y - 2^w) mod 2^w == y,所以等式永远相等
(y<2^(w -1)-1, 所以y-2^w需要w+1位来表示,那么阶段到w位,就是把w+1位丢掉,就是正好的-2^w)
32
当x = 0x7fffffff; y=0x80000000;的时候会出错。因为补码负数和正数表示的区间不一样。
以char类型为例,x=0x7f=127, y=0x80=-128
-y = -(-128) = 128,但是128超过了补码正数的表示范围,128还是0x80,在补码计算的时候还是-128
所以计算 x-y的时候 127 - (-128) = 127 + 128 = 255,溢出了
但是计算 x + (-y)的时候为 127 + (-128) = -1,没有溢出。
int tsub_ok(int x, int y){
int sub = x - y;
return (x >= y)?(sub >= 0):(sub < 0);
}
33
x |
x |
-x |
-x |
十六进制 |
十进制 |
十进制 |
十六进制 |
0 |
0 |
0 |
0 |
5 |
5 |
-5 |
B |
8 |
-8 |
-8 |
8 |
D |
-3 |
3 |
3 |
F |
-1 |
1 |
1 |
34
w = 3, 2^3 = 8, 2^6=64
模式 |
x |
y |
x*y |
截断的x*y |
无符号 补码 |
[100] = 4 [100] = -4 |
[101] = 5 [101] = -3 |
20 = [010100] 12 = [001100] |
[100] = 4 [100] = -4 |
无符号 补码 |
[010] = 2 [010] = 2 |
[111] = 7 [111] = -1 |
14 = [001110] -2 = [111110] |
[110] = 6 [110] = -2 |
无符号 补码 |
[110] = 6 [110] = -2 |
[110] = 6 [110] = -2 |
36 = [100100] 4 = [000100] |
[100] = 4 [100] = -4 |
35
这里用**来代表算术上的乘法,而*是计算机上的乘法指令。
假设x,y一共有w位
p = x * y
*乘法的规则是截断高位。如果溢出的话,必然至少存在一个t不为零,u > w 使得 x ** y = p + t2^u;
q = p/x. 根据取模的运算法则,可以得到 p = x**q + r. 这里r是余数,也就是说|r| < |x|,也就是说r可以使用w位来表示。
当q = y的时候:
x**y = p + t2^u => p = x **y -t2^u
p = x**q +r => p = x**y + r
那么就得到 r = -t2^u. 因为 u >w,并且r只需要w位就能表示,那么必然t = r =0
结论如果 p/x ==y 为真可以推导出 t = r = 0, 没有溢出
如果t = r = 0 也可以推导出没有溢出,p/x ==y为真。
所以为充分必要条件.
36
int tmult_ok(int x,int y){
long long mult = (long long)x * y;
return !(mult > INT_MAX || mult < INT_MIN);
}
37
A 这段代码使用long long unsigned来计算乘法,所以乘法本身不会产生溢出了。但是malloc本身的参数类型是size_t,所以依然会有一个截断的问题。
B 使用一个条件判断,如果asize过大的话,函数直接返回NULL
38
可以计算 2^k次倍数,和2^k + 1的倍数.
39
如果位置n为最高位,那么x<<n+1就直接溢出了。所以改为 x<<n - x<<m + x<<n
当然,在n为最高位的时候,只有x=1的时候才不会左移溢出.
考虑w=4的时候,当n为最高位的时候1000 = 8,那么只有1<<4 = 8是不溢出的。2<<4 = 16, 已经溢出了.
40
K |
移位 |
加法/减法 |
表达式 |
6 |
2 |
1 |
x<<1 + x<<2 (6 = 2+4) |
31 |
1 |
1 |
x<<6 - x (31 = 32-1) |
-6 |
2 |
1 |
x<<1 - x<<3 (-6 = 2-8) |
55 |
2 |
2 |
x<<7 - x<<4 - x (55=64 - 8 - 1) |
41
如果n为最高有效位,那么选择A。因为n+1会溢出。
其他情况,如果n-m>=3的话,使用形式B,因为计算量少。
42
int div16(int x)
{
return (x + ((x >> 31) & 0x0F)) >> 4;
}
43
M=31,N=8
44
A: 恒为1
B: 恒为1
C: 假设x = 2^16 + 2^14.
(2^16 + 2^14) * (2^16 + 2^14)= (2^16)^2 + 2*2^16*2^14 + (2^14)^2 = 2^32 +2^31 + 2^28
2^32被截断舍去了,2^31是符号位,为负数。
D: 恒为1
E: 0x80000000,也就最小的负数,再取负的话会溢出成为负数。
F:把公式2.5或2.6带进等式就可以证明恒为1了
G: x*~y + uy*ux ==>
x*(-y -1) + uy*ux ==>
-xy -x + uy*ux ==> -x
恒为1