第一章计算机系统漫游
比较基础广泛,后面有空补上。
第二章 信息的表示和处理
首先需要理解计算机当中的位的概念。
位:在二进制数系统中,位记为b,即比特,是最小的存储单位。0/1中每一位是1 bit 。
本章中研究三种最重要的数字表示形式:
- 无符号编码(unsigned)
- 补码(two’s-complement)
- 浮点数(floating-point)
先有个粗略印象:无符号编码实际上就是传统的进制转换表示方式,由于只表示正数,正常转换十进制数到二进制数就是无符号编码;补码用于表示有符号数(如负数),而浮点数编码需要用到以2为基数的表示方法,后面会详细介绍。
这里补充介绍一下溢出(overflow):
数据类型超过了计算机字长界限就会发生溢出的情况。
书中举例:500 * 400 * 300 * 200
有符号整型最大就是 0111 1111 1111 1111 1111 1111 1111 1111
一旦超过,自然发生正溢出成负数。
浮点数由于表示方法的不同,会溢出为正无穷。
整形表示精确但范围较小,浮点数表示近似但范围较大。
后面我们将在本章中学习计算机中数的表示方法、进制之间表示方式的转换以及直接操作数字的位级运算等,理解这些算数运算的特点、属性。
补充: C语言标准:初始GNU 89->ANSI C->C90(C89)->C99->C11
2.1 信息存储
这一小节我们将了解计算机中的信息存储方式。
首先了解字节的概念:大多数计算机中使用8位的块作为一字节(byte),是计算机中最小的存址单位。
计算机中将内存视为一个非常大的字节数组,称为虚拟内存。
内存中的每个字节都有一个唯一的数字来标识,称为地址。
内存存值,自有地址,按址寻值。
所有可能的地址集合称为虚拟地址空间(Virtual Address Space)。
需要注意虚拟地址空间与虚拟内存的区别!!
程序对象:程序数据、指令与控制信息。
程序对象的管理完全是在虚拟地址空间中进行的。
书中的举例:C语言中每个指针的值都是指向某个存储块第一个字节的地址。
核心:每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。
补充:指针的理解
指针提供了引用数据结构元素的机制。
指针包含值和类型,类型由编译器维护,而机器级代码中并不体现。
值只是存储对应对象的位置(往往某个存储块第一个字节的位置)
类型则表示存储对象的类型。
第三章中将结合指针在机器级代码上的表示与实现进行解释。
2.1.1十六进制表示法
这一节其实相当简单,主要是进制转换与表示方法的记忆与理解。
掌握目标应该是形成主观直觉,一眼能完成转换。
十六进制转换不用在意大小写,0-9+A-F来表示。
记忆几个特殊的:
A:1010 C:1100 F:1111
由此易推出:
9:1001 B:1011 D:1101 E:1110
二进制转十六进制就是每四位转一个对应数字(包括A-F),并在开头加上0x作为hex encode的标志。
十六进制转二进制就是每一个对应数字转四位二进制字符即可,也是快速且简单的。
十进制转二进制建议自行百度,初中知识。
有趣的一点在于最后除出来的余数做高位的原因,大家可以悟一悟。
从除的次数更多的角度去想。
此节结束。
这里建议完成练习题2.3
十进制 | 二进制 |
---|---|
0 | 0000 0000 |
167 | 1010 0111 |
62 | 0011 1110 |
188 | 1011 1100 |
55 | 0011 0111 |
136 | 1000 1000 |
243 | 1111 0011 |
82 | 0101 0010 |
172 | 1010 1100 |
231 | 1110 0111 |
二进制 | 十六进制 |
---|---|
0000 0000 | 0x00 |
1010 0111 | 0xA7 |
0011 1110 | 0x3E |
1011 1100 | 0xBC |
0011 0111 | 0x37 |
1000 1000 | 0x88 |
1111 0011 | 0xF3 |
0101 0010 | 0x52 |
1010 1100 | 0xAC |
1110 0111 | 0xE7 |
2.1.2 字数据大小
字长(word size):是指明指针数据的标称大小。
书上翻译的概念显得模糊而抽象。
实际上所谓指明指针数据的标称大小即指针范围的大小。
直接理解为字节的长度范围即可。
字长主要影响的是虚拟地址空间,一个字长为w的机器虚拟地址的范围为0~2^w-1,能访问的字节自然只有2 ^ w个,共2 ^ (w+3) bit。
补充
32位:4GB
64位:16EB
三十二位程序可以在六十四位机器上运行,这叫做向后兼容。
六十四位程序只能在六十四位机器上运行。
所以区别“三十二位”还是“六十四位”程序的根据在于编译方式,而不是运行机器。
关于为何64位机器不能用int类型存储一个指针
简单地说就是在六十四位机器中int占4 byte,但存储int的地址竟然是8 byte,超过了int的4 byte。
2.1.3 寻址和字节顺序
首先我先用好理解的白话简单讲一讲地址存储的概念。
我们知道一个byte是8个bits,在计算机当中有一个叫虚拟空间地址的东西,每个对应的数(表示为十六进制)如0x107,指向一个byte的存储地址。而存储一个变量,如int类型的指针指向的是该变量的起始存储位置,如指针指向0x102,则0x103、0x104、0x105连续的三个bytes共同存储该int类型变量,计算机当中字节序列的存储地址是连续的。
然后我们来讲一讲字节顺序,从我们的标题你也应该猜到了。
这里就必须提及计算机界相当出名的两个存储顺序:大端法和小端法。
其实从名字入手就很好理解,如一个int类型变量的十六进制值为:0x00230012,它的高位是00,低位是12。如果起始存储位置从00开始存,就叫做大端法存储;如果起始位置从12开始村,就叫做小端法存储。Intel大多支持小端模式,而IBM与Oracle大多支持大端模式。
一般进行网络传输都会在传输前进行格式的正确转换与表达,这样不会出现冲突与错误。
大端小端的区别主要在三种情况下需要格外注意。
第一种就是网络传输不同机器的解释规则不同易造成错误;
第二种就是阅读机器级代码时提前知道该机器的存储格式也非常重要,不然容易引发理解错误;
第三种就是当编写规避正常的类型系统的程序时。
前面两种都很好理解,第三种是什么意思呢?
所谓规避正常的类型系统,实际上就是指类型的强制转换。
转换后十六进制值会改变,但是在不同机器上的十六进制值是相同的,但是不同机器上的指针值完全不同,并且这个时候大小端的不同表示可以很轻松地看出来。
原书中用了一段C的代码来演示这一过程。
其实真正的原因是:指针总是指向第一个字节序列地址,如果表示方法不同,则指向同样会不同(从头到尾/从尾到头)。
第三种有两方面比较特别。一是可以通过(如书中源代码)打印地址与变量值的方式确定机器存储模式是大端还是小端存储;二是通过指针值的不同体现出不同机器系统配置使用了不同的存储分配规则。
可以类比第二种情况细细体会,打印的地址值与存储值在你不知道机器类型、实际值大小时,你怎么判断真实值大小?
补充:
执行强制类型转换时不会改变实际地址的值,只是重新告诉编译器该用怎样的数据类型来看待被该指针指向的数据。
使用命令 man ascii可以得到一张ascii码表(没啥用)。
练习题 2.5
(后面的文章都不再贴出题目,请结合书本或者电子版观看)
小端法 | 大端法 |
---|---|
21 | 87 |
21 43 | 87 65 |
21 43 65 | 87 65 43 |
练习题 2.6
A:0000 0000 0011 0101 1001 0001 0100 0001
0100 1010 0101 0110 0100 0101 0000 0100
B:一共有21位相匹配
C:int的二进制表示中除了前面11位以外都匹配,float的二进制中除了最后2位与前面9位都匹配。
C更标准:int除了最高有效位其余低位都匹配,float部分有效高位不匹配。
2.1.4 表示字符串
其实就讲了两个细节:
第一是C语言字符串会默认用null(其值为0)作为结尾。
第二是ascii码编码的字符相对于二进制数据具有更强的平台可移植性。
练习题 2.7
61 62 63 64 65 66 00
2.1.5 表示代码
讲了一件事情:
不同系统与机器生成的二进制文件,也就是机器代码是不同的。
原因是不同机器类型使用不同的且不兼容的指令和编码方式。
所以就能理解为什么一些文件只能linux打开了吧。。还得是64位。
2.1.6 布尔代数简介
其实就讲了四种位运算。
~取非,^取异或,&取且,|取或。
所谓扩展到位向量,其实就是指可以一串二进制数据可以进行上述操作罢了。
练习题 2.8
运算 | 结果 |
---|---|
a | 01101001 |
b | 01010101 |
~a | 10010110 |
~b | 10101010 |
a&b | 01000001 |
a|b | 01111101 |
a^b | 00111100 |
布尔代数与布尔环
位向量在进行布尔运算过程中就形成了布尔代数。
对于长度为2^w位的位向量,进行布尔运算时我们将这种数学形式称为布尔环。布尔环可以用集合的运算形式表示,|表示并集,&表示交集,^表示补集。
A:
颜色 | 补 |
---|---|
黑色 | 白色 |
蓝色 | 黄色 |
绿色 | 红紫色 |
蓝绿色 | 红色 |
红色 | 蓝绿色 |
红紫色 | 绿色 |
黄色 | 蓝色 |
白色 | 黑色 |
B:
运算 | 结果 |
---|---|
蓝色 | 绿色 | 蓝绿色 |
黄色 & 蓝绿色 | 绿色 |
红色 ^ 红紫色 | 蓝色 |
2.1.7 C语言中的位级运算
这里主要讲了一件事,C语言也能进行上面所说的位级运算。
还有a^a=0, a ^ b ^ a=b;
练习题 2.10
步骤 | *a | *b |
---|---|---|
初始 | a | b |
第一步 | a | a^b |
第二步 | b | a^b |
第三步 | b | a |
练习题 2.11
A:first=last=k
B : 此时first=last,原理相当于a^a=0
C : 循环体中first<=last改为first<last
掩码运算:顾名思义,就是屏蔽部分输入位的运算。
如0x87654321 & 0xff = 0x00000021
十六进制进行布尔运算可先化为二进制,注意位之间的关系。
练习题 2.12
A:x&oxFF
B: x ^ ~oxFF
C: x | oxFF
注意一个数^0则是本身, ^ 1 则是取反。
练习题 2.13
bool_or: int result = bis(x,y)
bool_xor: int result= bis(bic(x,y),bic(y,x))
注意一个点: x^y = (~x & y) | (x & ~y)
2.1.8 C语言中的逻辑运算
其实这一小节就讲了逻辑运算与按位运算是不同的,相信写程序的各位都能体会到。
第一个不同是逻辑运算只会返回True 或者 False两种结果,只有在特殊情况(0x00 0x01)才会返回与按位运算相同的结果。
第二个不同就是逻辑运算存在短路,这一点可以融合到编程习惯中。
练习题 2.14
表达式 | 值 |
---|---|
x&y | 0x20 |
x | y |
~x | ~y | oxDF |
x&!y | 0x00 |
x&&y | 0x01 |
x||y | ox01 |
!x||!y | 0x00 |
x&&~y | 0x01 |
这里需要注意的有两点:
1、C中两个十六进制(除0x00 0x01)做逻辑运算(&& ||)返回值为0x01;
2、注意逻辑运算的短路特点,同时~是按位非,!是逻辑非,返回值完全不同!
2.1.9 C语言中的移位运算
移位运算整体来讲分两种,左移与右移,左移只有一种情况,移动几位右边全部补0.如 00010011<<2 = 0100 1100
但是右移则分两种情况:逻辑右移与算数右移
逻辑右移全部补0,算数右移全部补最高位的值。
C中无符号数全部采用逻辑右移,常数全部采用算数右移。
Java中>>是算数右移,>>>是逻辑右移。
同时C中尽量不要超过总位数移动,Java会严格取模,不存在问题。
最后注意运算先后顺序,加法的优先级都高于移位运算的优先级。
基本上可以这样计算:左移一位大两倍,算数右移一位小两倍。
练习题 2.16 略
(以后只会选取觉得有意义的题目,不然太浪费时间了。。。)
第二章2.1节到此结束,后面会抽空更新2.2节 整数表示。