计算机中信息的表示和处理

1. 现代计算机存储和处理的信息以二值信号表示。

2. 计算机用不同的表示方法实现算术运算,例如加法和乘法,类似于对应的整数和实数运算。

  无符号(unsigned)编码基于传统的二进制表示法,表示大于或等于零的数字。

  补码(two's-complement)编码是表示有符号整数的常见的方式,有符号整数就是可以为正或者为负的数字。

  浮点数(floating-point)编码是表示实数的科学计数法的以二为基数的版本。

3. 计算机的表示法实用有限数量的位来对一个数字编码,因此,当结果太大以至不能表示时,某些运算就会溢出(overflow)。

4. 整数的表示只能编码一个相对较小的数值范围,但是这种表示是精确的;而浮点数虽然可以编码一个较大的数值范围,但是这种表示只是近似的。

5. 大多数计算机使用8位的块,或者字节(byte),作为最小的可寻址的存储器单位,而不是在存储器中访问单独的位。

6. 机器级程序江存储器视为一个非常大的字节数组,称为虚拟存储器(virtual memory)。存储器的每个字节都由一个唯一的数字来标识,称为它的地址(address),所有可能地址的集合称为虚拟地址空间(virtual address space)。这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际的实现是将随机访问存储器(RAM)、磁盘存储器、特殊硬件和操作系统软件结合起来,为程序提供了一个统一的字节数组。

7. 指针是C语言中一个重要特性。它提供了引用数据结构(包括数组)的元素机制。与变量类似,指针也有两个方面:类型和值。它的值表示某个存储块的第一个字节的虚拟地址,而它的类型表示那个位置上所存储对象的类型(比如整数或者浮点数)。

8. 二进制、八进制、十六进制、与十进制等的转化。

9. 每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为w位的机器而言,虚拟地址的范围为0~2^w-1,程序最多访问2^w个字节。

10. 程序员应该力图使他们的程序在不同机器和编译器上是可移植的。可移植的一个方面就是使程序对不同数据类型的确切大小不敏感。

11. 很多人认为一个声明为int类型的程序对象能被用来存储一个指针,这在32位的机器上能正常工作,但是在64位的机器上却会导致问题。

12. C语言中指针类型的占用空间大小与操作系统位数有关,char* 在32为系统中占4字节,在64位系统中占8字节。(我分析与编译器也有关,因为我在64位系统上实验发现char*还是4个字节。如果哪位读者有确切的答案,请告知iddmx(dmxmails@gmail.com),感谢您的分享)。

13. 对于跨越多字节的程序对象,我们必须建立两个规则:这个对象的地址是什么,以及在存储器中如何排列这些字节。

14. 在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用的字节中最小的地址。

15. 排列表示一个对象的字节有两种通用的规则——小端法(little endia)和大端法(big endia)。

16. 对于大多数应用程序员来说,他们机器所使用的字节顺序是完全不可见的,无论为哪种类型的机器编译的程序都会得到相同的结果。不过有时候,字节顺序会成为问题:

  1)在不同类型的机器之间通过网络传送二进制数据时,一个常见的问题是当小端法机器产生的数据被发送到大端法机器或者反方向发送时会发现,接收程序字里的字节成了反序的。

  2)当阅读表示整数数据的字节序列时,字节顺序也很重要。

17. 下面代码使用强制类型转换来访问和打印不同程序对象的字节表示。

 1 #include <stdio.h>
 2 
 3 typedef unsigned char* byte_pointer;
 4 
 5 void show_bytes(byte_pointer start, int len) {
 6     int i;
 7     for (i = 0; i < len; i++)
 8         printf(" %.2x", start[i]);
 9     printf("\n");    
10 }
11 
12 void show_int(int x){
13     show_bytes((byte_pointer) &x, sizeof(int));
14 }
15 
16 void show_float(float x){
17     show_bytes((byte_pointer) &x, sizeof(float));
18 }
19 
20 void show_pointer(void *x){
21     show_bytes((byte_pointer) &x, sizeof(void *));
22 }

  注:代码直接在博客的文本编辑器下手打,并未编译,不保证拼写完全正确。

18. C语言中对于指针的强制类型转换不会改变真实的指针,只是告诉编译器以新的数据类型来看待被指向的数据。

19. C语言中字符串被编码为一个以null(其值为 0)字符结尾的字符数组。每个字符都是由某个标准编码来表示,最常见的是ASC2字符码。

20. 在使用ASC2码作为字符码的任何系统上都将得到相同的结果,与字节顺序和字大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性。

21. UTF-8 表示将每个字符编码为 1 个字节序列,这样标准 ASC2字符还是使用和它们在 ASC2 中一样的单字节编码,这也就意味着所有的ASC2字节序列用 ASC2 码表示和用 UTF-8表示是一样的。

22. 不同的机器类型使用不同的且不兼容的指令和编码方式。即使是完全一样的进程运行在不同的操作系统上也会由不同的编码规则,因此二进制代码是不兼容的。二进制代码很少能在不同机器和操作系统组合之间移植。

23. 布尔运算 ~ &  |  ^ ,不清楚的读者可以百度,有一些有意思的特性,许多聪明的技巧 。

24. C语言一个很有用的特性就是它支持按位布尔运算。事实上,我们在布尔运算中使用的那些符号就是C语言所使用的: |(或),&(与),~(取反),^(异或)。这些运算能够运用到任何“整形”的数据类型上,也就是那些声明为 char 或者 int 的数据类型,无论它们有没有 short 、long 、long long 或者 unsigned 这样的限定词。

25.

  交换两个数的值 

void inplace_swap(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--)
        inplace_swap(&a[first],&a[last]);
}

  注:代码直接在博客的文本编辑器下手打,并未编译,不保证拼写完全正确。

26. 位级运算的一个常见用法就是实现掩码运算。

27. C语言提供了逻辑运算符,逻辑运算不要和位级运算混淆,他们功能完全不同。逻辑运算认为所有非零的参数都表示TRUE,而参数零表示FALSE。逻辑运算符 && 和 || 与它们对应的位级运算符 & 和 | 之间的第二个重要区别是,如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。因此,例如表达式 a && 5/a 将不会造成被零除,而表达式 p&&*p++也不会导致间接引用空指针。

28. 注意各种运算符号的优先级,拿不准的时候,加上括号总是好的。

29. 编码整数基本上有两种不同的方式:一种只能表示非负数,而另一种能表示负数、零和整数。

30. C和C++都支持有符号(默认)和无符号数。Java只支持有符号数。

31. 无符号数的编码:

  假设一个整数数据类型有w位。我们可以用一个函数B2U(Bianry to Unsigned)的缩写来表示:

    B2U(x)= ∑ xi*2^i (0<=i <= w-1)

  其实就是求出二进制的数在十进制里的值而已,很简单。

32. 有符号数的编码:

  注:由于输入公式不方便 xw-1 和 xi 中的 w-1 与 i 都为下标。

  1)补码编码:最常见的有符号的数的计算机表示方式就是补码(two's-complement)形式。在这个定义中,将字的最高有效位解释为负权(negative weight)。我们用函数B2T(Binary to Two's-complement的缩写)来表示:

    B2T(x)= - xw-1* 2^(w-1) +  ∑ xi*2^i (0<=i <= w-2)

    公式看起来好像很复杂,其实很容易理解。可以明显看出,上面公式的最小值就是第一位为 1 ,其余全是 0 的情况,为负值中最小。最大值就是第一位为 0 ,其余全是 1 ,所以补码表示的负值和正值的边界不等,负数的范围比正数的范围大 1 。

  2)反码:除了最高有效位的权是 -(2^(w-1) - 1)而不是 - 2^(w-1),它和补码是一样的:

    B2O(x)= - xw-1*(2^(w-1)-1) +  ∑ xi*2^i (0<=i <= w-2)

  3)原码:最高有效位是符号位,用来确定剩下的位应该取负权还是正权:

    B2S(x)=  (-1)^(xw-1)*∑ xi*2^i (0<=i <= w-2)

33. 关于整数数据类型的取值范围和表示,Java 标准是非常明确的。它要求采用补码表示。在Java中,单字节数据类型称为byte,而不是char,而且没有long long 数据类型。这些非常具体的要求都是为了保证无论在什么机器上,Java程序运行的表现都一样。

34. 反汇编器是一种将可执行程序文件转换成可读性更好的ASC2码形式(个人猜测是汇编语言)的程序。

35. 有符号数和无符号数之间的切换:

  强制类型转换的结果是保持位值不变,只是改变了解释这些位的方式。如 v = -12345 , uv = 53191 ,可以看到 -12345 的 16 位补码表示与 53191 的16位无符号表示是完全一样的。

  原书中对于两者转换有详细的推导,我并没有仔细思考,这里也就不献丑了,如果您想了解,请查阅相关资料。

36. 原书中对于扩展一个数字的位表示、截断数字有详细的推导和讲解,我并没有仔细思考,这里也就不献丑了,如果您想了解,请查阅相关资料。

37. 有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。比如:

  这段代码想要计算数组a中所有元素的和,元素的数量由参数length给出。

/* This is buggy code */
float sum_elements(float a[], unsigned length) {
    int i;
    float result = 0;

    for(i = 0; i<=length-1; i++)
        result += a[i];
    return result;   
}

  当参数length=0时,应该返回0.0 。但实际上,运行时会遇到一个存储器错误。

  因为参数length是无符号的,停止条件 i<=length-1 计算0-1 将进行无符号运算,等价于模数加法。结果得到UMax,超出数组范围。

  避免这类错误的一种方法就是绝不使用无符号数。

38. 整数运算,由于涉及到太多公式。。。。我表示很无力啊。。。。不会输入到这里。。。

39. 浮点数,关于浮点数的表示,可以参考该链接 http://bbs.pediy.com/showthread.php?t=55241

声明:

  本文为 iddmx 对《Computer Systems : A Programmer's Perspective》(Second Edition) Chapter 2 | Representing and Manipulating information 的读书笔记。内容大部分都是书中内容,也有一些个人理解,由于 iddmx 水平有限,书中许多精华没能提取出来,如果想要深入了解,请阅读原书。

  本文欢迎自由转载,但请务必保持本文完整或注明来之本文。本文未经 iddmx 同意,不得用于商业用途。最后,如果您能从这个简单文档里获得些许帮助,iddmx 将对自己的一点努力感到非常高兴;iddmx 水平有限,如果本文中包含的错误给您造成了不便,iddmx 在此提前说声抱歉,并希望您能j将错误告知 iddmx(dmxmails@gmail.com),以便修正。

  祝身体健康,工作顺利。☺

 

posted @ 2012-12-21 12:14  iddmx  阅读(973)  评论(0编辑  收藏  举报