csapp读书笔记2-信息的表示和处理

信息存储

计算机使用二进制数字存储数据,当数据要被使用时,就会被加载进内存。计算机获取这些数据的方式就是在内存中寻址,寻址的最小单位是字节(8个bit位)。操作系统将主存和IO设备抽象为一个大的字节数组,称为虚拟内存,其中每个字节都有唯一标识,称为地址,这些地址组成的集合被称为虚拟地址空间

十六进制

计算机采用二进制数字表示数据,这样很冗长,但是书中会以十六进制来表示,这样更加简洁,之所以不采用十进制是因为二进制转换十进制较麻烦

字:计算机将数据切分成字,每次只能处理一个字的数据

字长:每个字包含的位数,通常有32位字长机器和64位字长机器

虚拟地址以一个字来编码,计算机通过虚拟地址操作数据。字长w的虚拟空间地址范围为0 ~ 2^w -1。32位机器虚拟空间上限为4GB,64位机器虚拟空间上限为16EB

32位程序和64位程序代表了该程序是以32位编译还是以64位编译的,跟所在机器位数没关系(32位机器可以采用64位编译,64位机器可以采用32位编译)。C语言有些数据类型,在32位程序和64位程序所占用的字节数也不同(如long在32位占4字节,而在64位站8字节)。程序员应该使用准确的类型(int32_t和int64_t)来保证代码可移植性

寻址和字节顺序

通常多字节对象被存储为连续的字节序列

对象值的存储方式有两种:小端法和大端法。主流机器都是小端

如int类型数字a,它的值用十六进制表示为0x01234567

小端法:值的高位在前,低位在后;大端法:值的低位在前,高位在后

编码

文本文件可以跨系统移植,而二进制文件往往不能

位运算与逻辑运算

位运算符:&(与),|(或),^(异或),~(非)

逻辑运算符:&&(与),||(或),!(非)

逻辑运算符任务所有非零数字都是TRUE,0则是FALSE,在计算机中分别用1和0代表,完成计算后也会返回TRUE或者FALSE

逻辑运算符&&,||自左到右计算,能够根据某个表达式提前确定结果

移位

  • 左移:<<,右边补0
  • 逻辑右移:>>,c语言的无符号数>>和java的>>>,左边补0
  • 算术右移:>>,c语言的有符号数>>,左边补符号位

无符号编码

w位二无符号可表示0 ~ 2^w-1之间的整数

补码编码

最高位为“负权重”,当最高位是1的时候代表是负数,除去最高位之后的数越大,实际数值越小,除去最高位的数越小,实际数值越大

w位补码可表示-2^(w-1) ~ 2^(w-1)-1之间的整数

有符号和无符号之间的互相转换

补码可看做有符号数,上图表明了有符号到无符号的转换示意图

  • 当数值在0 ~ 2^(w-1)-1时,互相转换数值保持不变
  • 当数字在-2^(w-1) ~ -1时,有符号数转成-2^(w-1) ~ 2^w范围内的无符号数,反之亦然

扩展数字的位表示(向上转型)

将数字从较小的数据类型转为较大的数据类型,如byte转为int就是从8位转到32位,可以自动转换而不产生精度丢失

图中所示在32位机器上的程序,sx表示short类型,usx表示unsigned short类型,x表示int类型,ux表示unsigned int类型。其中x、ux分别是从sx、usx转换而来

  • 无符号:扩展0
  • 有符号:扩展符号位

截断数字(向下转型)

将数字从较大的数据类型转为较小的数据类型,如int转为byte,可能会丢失精度

将x截断为k位的数值x'

  • 无符号:x' = x mod 2^k
  • 有符号:y = x mod 2^k ,将y转为k位有符号数值x',最高位k-1位符号位

关于有符号、无符号的使用建议

有符号数和无符号数做运算时,c语言会将有符号数转为无符号数,这时可能导致转换后数值发生变化,导致bug产生,所以涉及数值运算尽量使用无符号数值。如果只是把数中的位当成一种标记或者掩码,无符号数值往往很有用

无符号加法

当x+y溢出时,会丢弃最高位,结果等于x+y的实际值减去2^w

检测溢出:当结果小于x或y时,就出现溢出

有符号加法

实际溢出情况分为两种:

  • 正溢出:正溢出会导致w-1位为1。为了方便计算,这个数扩展1位保证数值不变,即得w位为1,剩余w-1位就是实际x+y的值,最后生成的结果就是x+y-2^w(实际中并没有真正扩展1位,一旦扩展最高位就会被丢弃)
  • 负溢出:负溢出会导致w位为1,w位超过了有符号类型的上限,会被截取,相当于x+y的实际值加上2^w才是最终结果

检测溢出:当x>0,y>0时,如果结果<=0,则溢出;当x<0,y<0时,如果结果>=0,则溢出

无符号、有符号乘法

两者都需要将结果截断为k位,但是有符号还需要将生成的数转换成无符号数。可以将相乘的结果保存到64位整数,以防止这种溢出现象

### 乘以常数

大多数机器上整数乘法很慢,往往需要10个周期以上,其他整数运算(加减、位运算)只需要1个周期。编译器往往会通过移位和加减组合来替代乘法

比如10=2^3+2^1,编译器会将x*10优化为(x<<3)+(x<<1)

大多数编译器在只要少量移位和加减时才会采用这项优化

除以常数

大多数机器上整数除法比乘法更慢,往往需要30个周期以上。而且编译器也没办法优化除以任意常数,目前只能做到对除以2的幂做移位运算优化

整数除法总是舍入到0,如3.14 -> 3,-3.14 -> -3,3 -> 3,总结就是正数向下舍入,负数向上舍入

算术右移会统一以向下舍入,导致负数右移和除法产生不同的结果,如-3/2=-1,而-3>>1=-2。所以实际中不会采用简单的算术右移N位来计算除以2的N次幂

根据y=x/(2^k)分情况讨论:

  • 无符号数除以2的幂:将x逻辑右移k位
  • 有符号数除以2的幂:x小于0时,采用(x+(1<<k)-1)>>k;否则,采用x>>k

针对上述公式说明:负数右移可能丢失部分位,导致值更小,也就是向下舍入,为了让它也能向上舍入,需要加上一个偏移量(1<<k)-1,对于不需要舍入的情况这个偏移量最终会移位掉,对于需要舍入的情况这个偏移量会让k位左边的位加1,保证右移k位之后值更大,也就是向上舍入

浮点数

现在浮点数都是IEEE标准的浮点格式数字,统一的标准保证了浮点数在不同机器上的可移植性

二进制小数

图中所列的是一串二进制序列,以小数点做分隔,模拟十进制浮点数的展现形式,这个序列就是二进制小数

这个序列的代表的数值为:

二进制小数拥有二进制数的特性,小数点右移N位代表除以2^N,左移N位代表乘以2^N

二进制小数表达数字能力有限,只有不断提高二进制数长度才能提高精度,接近某些数值

IEEE浮点表示

二进制小数表示很大的数字时会非常占用内存,比如5*(2^100),采用二进制表示法就需要在二进制101后跟100个0

改进:使用数字x和y,x=5,y=100,上述大数可以表示为x*(2^y)

IEEE浮点标准用V=(-1)^s * M * 2^E表示一个数

  • 符号s:决定这个数是正数(s=0)还是负数(s=1),采用一位表示
  • 尾数M:二进制小数,范围在1-2间或者0-1之间。采用二进制序列frac表示
  • 阶码E:对浮点数加权重,可以为负数。采用二进制序列exp表示

c语言将浮点数分为三个部分,分别进行编码

  • 单精度float:32位,1位表示s,8位表示E,23位表示M
  • 双精度double:64位,1位表示s,11位表示E,52位表示M

根据阶码的不同,浮点数被分为四类:

  • 规格化:表示不为零的浮点数,E取值范围-126~+127,M取值范围1~2
  • 非规格化:全为0时表示+0.0;s为1其他为0表示-0.0;M不全为0时表示非常接近0.0的数字
  • 无穷大:当E全为1,M全为0时,根据s是0还是1决定表示正无穷大或负无穷大
  • NAN:当E全为1,M不全为0时,表示非法数字,即NAN

舍入

如果IEEE浮点表示法无法精确表示实数,则需要进行舍入运算,默认采用四舍五入的方式

浮点数、整数转换

  • int到float,不会溢出,可能舍入
  • int或float转double,精确保留值
  • double转float,可能溢出,可能舍入
  • float或者double到int,向零舍入

总结

64位程序的优势在于可以突破32位程序具有的4GB地址限制

大多数机器对整数采用补码编码,对实数采用IEEE标准编码

无符号转有符号、有符号转无符号都有风险导致精度丢失,bug产生

受编码长度限制,实数运算可能导致精度丢失、数值溢出等现象

猜你喜欢

转载自my.oschina.net/u/1378920/blog/1093917
今日推荐