第二章 信息的表示和处理
2025-01-10 20:20:33 # CSAPP

第二章 信息的表示和处理

2.1.3 对强制类型转换访问和打印不同程序对象的字节表示的代码的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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_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); //12345.0
return 0;
}
  • 对于show_int(12345),show_int()函数接受的参数12345为int型,4B大小,在该函数内部调用show_bytes(),第一个参数会传入12345所在的存储地址(&x),并将该地址参数强制类型转换为byte_pointer型,这表明当执行show_bytes()内部的printf()时,不再以int型作为单位访问,而是以unsigned char为单位访问x所在存储单元中的数据,由于12345是以int型传入show_int()中的,所以会for循环四次访问x所在存储单元中的数据。也就是说,存储12345的x地址传入show_bytes()后仍旧是原地址,只不过在show_bytes()中访问时只能一个字节一个字节的进行访问,因为unsigned char类型大小为1B

  • 对于show_pointer(pval),show_pointer中传入的是12345所在存储单元的地址pval,在其内部传入show_bytes()中的第一个参数为指针的地址,即pval变量自身的地址,而不是pval所指向的地址,所以show_bytes()中会按unsigned char类型输出pval变量所存储的数据,循环次数为该指针的大小

2.1.7 练习题2.11 两数互换问题

原文前面讨论了利用异或交换两数的方法,但是对于下面的函数,运行结果有bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
void funSwap(int *x,int *y) {
*y = *x^*y;
*x = *x^*y;
*y = *x^*y;
}
void reverse_array(int a[],int cnt) {
int first,last;
for(first=0,last=cnt-1;first<=last;first++,last--)
funSwap(&a[first],&a[last]);
}
int main() {
int a[5] = {1,2,3,2,5};
reverse_array(a,5);
for(int i=0;i<5;i++)
printf("%d ",a[i]);
return 0;
}

之所以数组的长度为2*k+1时,第k+1个数为0,原因在于在最后一次for循环时,first和last均指向a[k],此时inplace_swap中的x和y指向了同一个地址, *x = *y。运行时第一个式子 *y = *x ^ *x = 0,第二个式子 *x = 0^0 = 0,第三个式子 *y = 0^0=0。为了避免first和last指向同一个数,可以去掉 ‘=’。

2.3.1 练习题2.27 判断溢出

依据原文上面中的推导过程,当$$s<x$$或$$s<y$$时发生溢出,所以

1
2
3
4
int uadd_ok(unsigned x,unsigned y) {
unsigned s = x+y;
return s>=x; //不溢出返回1
}

2.3.2 练习题2.31 我没笑~

1
2
3
4
5
/* WARNING: This code is buggy */
int tadd_ok(int x,int y) {
int sum = x+y;
return (sum-x==y) && (sum-y==x);
}

这题给人一种表面上的正确,但是实际是错误的。

由于 有符号整数溢出的行为 自然表现为模运算,所以:

  • 当x+y不溢出时,函数返回正确结果

  • 当x+y正溢出时,该数会截断为后32位,即sum = x+y-232,所以

    sum - x = x + y - 232 - x = y - 232 = ( y - 232 ) % 232 = y

    sum - y = x + y - 232 - y = x - 232 = ( x - 232 ) % 232 = x

    所以( sum - x == y ) && ( sum - y == x ) 恒成立

  • 当x+y负溢出时,sum = x+y+232,所以

    sum - x = x + y + 232 - x = y + 232 = ( y + 232 ) % 232 = y

    sum -y = x + y + 232 - y = x + 232 = ( x + 232 ) % 232 = x

    也是恒成立的

所以上述代码无论是否溢出,都恒成立,所以有问题。

2.3.2 练习题2.32 tsub_ok溢出问题

有一个特殊情况需要特别讨论,即y = -231时,因为-y=-231

  • x>0时,不妨设x = 1,则x - y真值为1+231,真值发生了溢出,但是在tadd_ok()中,

    x>0,y<0,返回的是1即没有溢出。

  • x<0时,不妨设x = -1,则x - y真值为-1+231,真值没有溢出,但是在tadd_ok()中,

    x<0,y<0,x+y = [1111……1111] + [1000……0000] = [0111……1111] >0,所以返回的是0即发生了溢出

所以需要在tsub_ok()单独处理这种情况

1
2
3
4
5
6
int tsub_ok(int x, int y) {
if (y < 0 && -y < 0) { /* y是否为最小负数 */
return x < 0;
}
return tadd_ok(x, -y);
}