《深入理解计算机系统》
第2章 信息的表示和处理
1.浮点运算有完全不同于数学属性。
例如:在C语言中,表达式(3.14+1e20)-1e20的值为0.0,按照数学属性应该为3.14。原因是由于在机器中由于浮点数值一个近似表达,所以在括号中的值为1e20再减1e20就变成0.0了。若想实现值为3.14,表达式应该改为3.14+(1e20 -1e20)。
2.指针的值和类型含义
(1) 指针的值表示某个对象的位置。
(2)指针的类型表示指向位置上所存储对象的类型是哪种数据(比如:int or char)。
3.各种进制表示数据之间的转换
十进制数(x)转二进制数:将该十进制数除以2,然后每次得到的余数作为二进制表示的最低位,然后用本次的商继续除以2,余数为二进制的次低位,依次类推,直到商为0。
实例1:25 = 0001 1001
求解过程:
25除以2余1(2进制的最低位),上一次的商为12,12除以2余0,6除以2余0,3除以2余1,1除以2余1, 商为0,退出。
实例2:25 = 0x19
25除以16余9(16进制的最低位),上一次的商为1,1除以16余1, 商为0,退出。
4. 字长:指明指针数据的标称大小。 字长决定的最重要的系统参数就是虚拟地址空间的最大大小。一个字长为w位的机器,其虚拟地址的范围为0-2^w -1,最多访问2^w个字节。
例如:32位的机器的虚拟地址空间为:2^32byte/1024/1024/1024 = 4GB
5.同一种数据类型,在不同位数的编译机下编译的结果可能存在不同。例如:long int数据类型在32位机器中占4byte,在64位机器中占8byte。为了避免上述特性,C语言引入了一组数据类型,可以避免因机器不同而导致所需字节数不同,比如:int32_t在32位和64位都占用4byte,int64_t占用8byte。
6.大小端数据表示
例如一个数据为0x01234567
(1)大端法表示为0x01234567
大端法为“高位低地址”。假设地址显示为从左往右地址是增加的,即左边是低地址,右边是高地址。高位低地址”即数值的高位放在内存的低地址上,也就是01放在最左边的显示。
(2)小端法表示为0x67452301
小端法为“低位低地址”,也就是将数值的最低位(67)放在内存的低地址上(相当于是新表示数值的最右边)。
采用哪种表示方法和操作系统强相关。
7.C语言的位级运算
(1)| (或):2|5= 0010 | 0101 = 0111 = 7
(2)& (and):2&5 = 0010 & 0101 = 0000 = 0
(3)(取反): 2 = ~(0010) = 1101 = - 3
(4)^(异或):2 ^ 5 = 0111 = 7
8.C语言的逻辑运算
(1) || (逻辑或)命题成立一个,则返回true。
(2) &&(逻辑与)命题j均成立,则返回true。
(3) !(逻辑非)命题若为true,则返回false。
注意:在逻辑运算中,认为非零即为true。
||和&&运算时,只要第1个表达式就能判断结果时,第二个表达式则不会计算,所谓“”“短路”效应。
9.C语言的移位运算
(1 )<< 左移:x<<k,将x像左移动k位,右边补k个0;
(2)>> 右移:x>>k,右移分两类。逻辑右移:将x右移k位,左边的高位补0。算术右移:将x右移k位,左端补齐k个最高有效位的值。
注意1:无符号数,右移必须采用逻辑右移;有符号数一般采用算术右移,才会得到自己想要的数学结果。
注意2:不管左移还是右移,都应该保持移位量k小于移位值的位数x(32位或64位)。若大于则会变成实际右移k mod 32余x,则最终右移x位。
以下内容均为整数
10.数的表示:
(1)原码就是早期用来表示数字的一种方式: 一个正数,转换为二进制位就是这个正数的原码。负数的绝对值转换成二进制位然后在高位补1就是这个负数的原码。在有符号数中,最高位为符号位,1表示为负数,0表示为正数。
(2)正数的反码和补码都与原码相同。
(3)负数的反码为对该数的原码除符号位外各位取反。
(4)负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1。即负数的补码为反码加1。
(5)注意:当运算中同时存在无符号数和有符号数时,C语言会隐式地将有符号数转换成无符号数。**在做标准算术运算并无差异,但在做比较运算(>或者<)时,会出错。**比如-1<2;数学角度是正确的,但是通过计算机就不对,假如数据类型为unsigned int和int类型(64位的机器,4字节,-1转无符号数是-1+2^32=4294967295U),转完再和2比较,显然是不对的了。
#include <stdio.h>
void main()
{
int num = -1;
unsigned int unum = 2;
if(num < unum)
{
printf("-1 小于 2,正确判断\n");
}
else
{
printf("判断条件不对\n"); //输出为:判断条件不对
}
printf("%d\n",num + unum); //输出为1
printf("%u\n",num + unum); //输出为1
}
11.补码转换为无符号数
一个有符号数x,x的字长是w位(8,16,32,64)。若x>=0,则转换成无符号数也是其本身。若x<0,转成符号数相当于最高位的符号位的权重由负变正,所以x = (x + 2^w)U,U表示为无符号数。所以一个unsigned int的-1转成无符号数为 -1 + 2 ^ 32 = 4294967295U。
12.无符号数转换为有符号数的补码
一个无符号数x,x的字长是w位(8,16,32,64)。若x<=2^w,有符号数的最大表示范围为(-2 ^ w ~ 2 ^ w -1),若x没有饱和,则转成有符号数还是其本身x。若x > 2 ^w,则转成有符号数为x = x -2 ^w。
例如无符号数(4byte)4294965295,大于2^32,则转成无符号数为-2001。
#include <stdio.h>
void main()
{
unsigned int unum = 4294965295;
printf("%d\n",(int)unum); //输出为-2001
}
13.扩展数字:由小字长的数据转换成更大字长的数据类型,补码采用符号位扩展。
例1:5的表示为:
char:0000 0101
short int : 0000 0000 0000 0101
例2:-5的表示
char:1111 1011
short int :1111 1111 1111 1011
14.截断数字:由一个长字数据类型转成更小字的数据。
比如:将short int 的16位数截断成8位的char类型。舍弃高8bit,得到的低8bit数据,其最高位为新的符号位。
例1:-3000的转换
short int 1111 0100 0100 1000
截断为char 0100 1000 新的8bit,符号位0,所以数值为72.
例2:-3873的转换
short int 1111 0000 1101 1111
截断为char 1101 1111 新的8bit,符号位1,所以数值为-33
如果是无符号数的截断成k位,则是将该值与2 ^ k求模。
#include <stdio.h>
void main()
{
short int num = -3000;
short int num2 = -3873;
printf("%d\n",(char)num); //输出为72
printf("%d\n",(char)num2); //输出为-33
}
注意:
(1)通过上面的学习,就能发现,在就进行数据类型的位扩展时,数值是不会改变的。但在数据截断的时候,很大程度造成数值溢出,导致数值的改变,因此这个截取需要慎重。
(2)在比较两个数的大小时,一般不写成A - B>0来判断,原因是A-B为负数时,这个也会成立,原因是A-B先得到负数再被隐式强转成无符号数变成一个很大的正数。所以一般改写为A>B,且保证A、B是同一类型的数据更为靠谱。
15.无符号数加法
对于x,y在[0,2^w)的范围内,x+y在不溢出的时候就是x+y的和;如果x+y的值大于2 ^ w,说明溢出了,则和为x+y-2 ^w。
注意:检测两个无符号数相加是否溢出了,可以通过判断和x+y < x或y,即和小于其中任意一个无符号数(x、y),说明发生溢出了。在编程时,可以作为一个检测,写成鲁棒性更好的代码。
16.无符号数求相反数
对于x在[0,2^w)范围,当x= 0,相反数-x = 0;当x>0时,相反数-x = 2 ^w - x。
示例:假如x是一个8位宽的无符号数,数值为16,其相反数为2 ^ 8 -16 = 240(1111 0000),既然是求相反数则是强转成有符号数,则为-16。
17.补码相加求和
当x,y在[-2^(w-1), 2 ^(w-1)-1]的范围,则和x+y的范围为[-2 ^ w, 2 ^w-2],可以看出两数之和可能需要w+1位来表示。
(1)当和x+y>=2 ^(w-1),则发生正溢出,此时和为x+y-2 ^ w;
(2)当和x+y< -2 ^(w-1),则发生负溢出,此时和为x+y+2 ^ w;
(3)当和x+y还在w位宽的有符号数范围内时,和为x+y。
注意:检查补码求和的溢出。当且仅当x,y均大于(小于)0,和s小于等于(大于等于)0,则说明是正(负)溢出。
18.无符号乘法
两个w位宽的数据x,y,乘积的范围为[0,(2 ^ w -1)^2),即值可能需要2w位宽来表示,但是C语言中的无符号乘法定义乘积返回w位宽。因此相当于将乘积截断成w的位宽数值。即最后数值等于乘积求模x*y%(2 ^w)。
19.乘法运算所需要的运行时间需要10个或者更多的时钟周期,整数除法比乘法需要更多的时钟周期,加减法和移位及位运算只需要一个时钟周期。因此尽量用其他运算代替乘法运算。
以下内容均为浮点数
20.int 、 float、double数据类型的数彼此之间进行强制类型转换时,程序改变数值和位模式的原则如下:
(1)从int转换为float,数字不会溢出,但是可能被舍入。
(2)从int或float转换为double,因为double有更大的范围,也有更大的精度,所以能够保留精确的数值。
(3)从double转换成float,因为范围要更小一些,所以值可能溢出成+∞或-∞。另外,由于精度较小,它还可能被舍入。
(4)从double或float转换成int,值将会向0截断。例如,1.999将被转换成1,而-1.999将被转换成-1。注意这种行为与舍入是非常不同的。进一步来说,值可能会溢出。C标准没有对这种情况进行指定固定的结果,但是在大部分机器上,结果将是TMax或TMin。
#include <stdio.h>
#include "math.h"
int main(void)
{
/* 如果把2^24+1这个int转化位float,就只能转换成最接近的2^24 */
/* 原因是float为32位,其中最高位为符号位1位,紧接着是8位阶码,后23位是尾数,所以超过2^24后,就无法精确了*/
int a = pow(2,24) + 1;
float b;
b = a;
printf("a = %d\n",a); //16777217
printf("b = %lf\n",b); //16777216.000000
}