深入理解计算机系统:信息的处理与表示(一)基础
之前每次写博客都会先扯一点最近的事情,这次也一样。这几天是清明节,但是哈尔滨一点也没有莺飞草长的感觉,原先计划的太阳岛骑行也因为意外而泡汤了,于是这两天也就在给导师做翻译了,然后抽闲继续学习上面提到的那本书。
说实话之前并没有打算写读书笔记的,但是,但是这本书实在是太经典了,看得我都想哭了(被感动的)。写笔记前,我想分别对CMU的这两位教授说:叔叔,我好想亲你一口啊!
花开两朵各表一枝,感动之余开始写作啦。
1 二进制
计算机存储数据的时候统一的二进制,除了01之外计算机什么也不认识(要不然怎么说计算机笨得要死呢)。一般来说存储整数的时候有两种,有符号数和无符号数。本文主要介绍的就是这两种整数啦,但是重点不在于介绍各种编码,码,码赛克,(*^__^*) 嘻嘻……想看码赛克的同学自觉去面壁
二进制一般而言太冗长了,所以为了方便表示整了个十六进制,每四个二进制数代表了一个十六进制数,二者之间对应关系如下:
那么考一下你啊,1100 1010 1101 1011 0011怎么用十六进制表示? 写不出来的也去面壁
书中提到了一个技巧,就是怎么把2的n次方快速的转换为十六进制?是这样的,先写一个数字 2的(n%4)次方 ,然后在后面补上(n/4)个0就好了。举个例子,2048(2的11次方)就可以表示为十六进制的800,当然了,写出0x800才是规范的,0x代表十六进制。
2 字长
介绍了一下二进制,然后我们继续看看 字长 的概念。
什么是字长呢?大家平时所说的32位机、64位机,32(64)就是代表了该计算机的字长。当然了,一般来说整数的标准长度以及指针的标准长度都是字长。与字长关系最密切的应该就是计算机里面的虚拟空间了,不过这个不是本文的内容,不谈它。
接下来就是计算机里面参见数字类型所占用的二进制位数了,参见下表:(注意char* 泛指指针)
我用红色标记的是一般比较容易被忘记的,比如说有的程序员认为可以用int型变量可以存储一个指针!这在32位机上是没错,但是64位机就不行啦。还有,注意一下long!!!!
3 大端模式
再来介绍一下大端模式,小端模式。
所谓大端小端指的是存储数据的顺序问题,你比如说我们需要在地址0x100的地方存放数据0x01234567,地址0x100为这个数据的地址。但是地址0x100所对应的物理空间只有一个字节,那么我们必然要占用地址0x101,0x102,0x103这三个字节来存放我们的数据。现在问题来了,怎么存?
由图可见,大端就是把数据中权重最大的部分存在前面(小地址),什么是权重大的部分呢?借用千分位百分位十分位的概念,千分位相对于百分位就是权重大,那么显然 0x01234567中01的权重最大。
其实大端小端有什么讲究么?说白了,没啥讲究,看你心情(或许应该看制造商的心情)。
当然了,既然不同的机器采用的端模式可能不一样,那么我们就有必要去关注关注这个大小端了,以下三种情况我们需要格外注意:
其一,网络传输数据的时候,一定要在协议中注明你是按照什么端模式来传输数据的!
其二,看机器代码的时候,比如有这么一段代码80483bd: 01 05 64 94 04 08,那么你反汇编之后获得的是这样的汇编代码add %eax,0x8049464。显然,这是个小端模式的机器。
其三,数据类型强制转换的时候。此时,编译器就不再管你是什么类型了,直接改地址。具体可以看看本博客的这个文章:用C语言判断CPU的端模式
4 布尔位运算
再来介绍布尔位运算。与、或、反、异或,都太基础了,不解释了。这里特别提到一个 位数组 的概念,举个例子吧
用[01101001]来编码集合 {0, 3, 5, 6},从右往左,第0,3,5,6位都被置1其余为0。位数组显然可以祈祷压缩空间的左右,而且对两个位数组执行 与操作 就等于对原先两个几个执行 交操作;或操作对于 并操作。
布尔位运算奇妙无穷,我们可以用它来实现不借助额外空间直接交换两个变量的值:
这段代码是基于一个观察,就是说a^a=0,任何数字异或其本身都将得到0。
看到这个之后就有同学很开心的根据此写出了如下一个将数组倒序的代码:
结果是不能。当然喽,要是数组大小为偶数是没问题的,为基数的时候就不对了1、2、3、4、5会倒序成5、4、0、2、1。哈哈奥妙就在于上述的就地交换代码中默认x,y是不同的变量。当x,y为同一个变量时,无论它是什么结果都为0!自己纠结原因去吧。
再来一个布尔位运算的应用,它可以用来提取原数字里面的指定一段,说的不太清楚哈,举个例子,怎么提取0x01234567里面的低八位。答案是0x01234567 & 0xFF
说完布尔位运算,接着说说布尔逻辑运算,就是&&,||,~这个就没什么好说的了,注意一点&&,||具有“短路效应”
5 移位运算
再来说说移位运算:
移位运算可能会被错用,比如说 1<<2 + 3<<4,这个的结果是什么?哈哈,考虑加号的优先级高于移位,相当于1<<5<<4,返回512。理解成52的孩子自觉去面壁。
6 整数的表示
好了,接着将整数的表示吧(终于快到正题了,~~~~(>_<)~~~~ )
先来一张表:
好的,我们来看看无符号二进制和十进制的转换:一图胜千言
再来看一下有符号数:(注意公式中Xi表示二进制表示中,从右往左第几位;w表示二进制的长度)
见识到图片的力量了吧,我就是从这时候开始被感动了,可是,可是这仅仅只是个开始!!
接下来看看几个特殊的值:
观察到没有,有符号数的-1和无符号数的最大值,他们的二进制表示居然一样!还有有符号数的最大值最小值居然不一样!
对了,注意一下TMin是怎么在C语言中表示的:
#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)
那么有符号数和无符号数之间怎么转换呢?
先看有符号数转化为无符号数:
公式是这样的:
再看看无符号数怎么转化为有符号数:
好了,至此完成了有符号数和无符号数之间的转换,下面介绍一下整数的扩展,举个例子,怎么将有符号数101扩展为8位。这个呢,记住无符号数直接在前面补0,有符号数直接在前面补 符号 。
我看难点在于怎么有符号数101(-3)扩展为4位的-3,见下图:
有时候我们会面对扩展和类型转换同时发生的情况,比如说将short int 转换为 unsigned int。此时会先进行扩展(short –> int)再转为unsigned int。所以:
short sx=-12345; (0x CFC7)
unsigned uy=sx;
会先将0x CFC7扩展为 0x FF FF CF C7 再转为无符号数。而不是先转为无符号数0x CFC7 再扩展为int (0x 00 00 CF C7)
7 无符号陷阱
最后我们来谈谈无符号陷阱。
第一个例子:
1: /* WARNING: This is buggy code */
2: float sum_elements(float a[], unsigned length) {
3: int i;
4: float result = 0;
5:
6: for (i = 0; i <= length-1; i++)
7: result += a[i];
8: return result;
9: }
这个有什么问题呢?想不到的去面壁
当传进来的length为负数的时候。。。。。
第二个例子:
1: /* Prototype for library function strlen */
2: size_t strlen(const char *s);
3:
4: /* Determine whether string s is longer than string t */
5: /* WARNING: This function is buggy */
6: int strlonger(char *s, char *t) {
7: return strlen(s) - strlen(t) > 0;
8: }
有什么问题呢?
当s比t短的时候,仍然返回真!
第三个例子:看看本博客的一篇博文 一个微妙的错误
好了本文终!下一篇文章会介绍加减乘除运算以及太多太多的拍案叫绝!!