深入理解计算机系统 第二章要点

1.每台机器都有一个字长,指明了整数和指针的标称大小(normal size),长整数(long int)和指针的大小都是字长(32位机器是32位,64位机器是64位),字长代表了机器的寻址时地址大小,进而限定了寻址空间的大小,字长w的寻址范围是0-2的(w-1)次方,32位机上线是4G。除了和字长同步的类型(long int和指针)其他类型大小和机器无关,比如char,short,int4,long long int8, float4, double8在32位机器上对8字节的类型数据操作时,先编译成一系列4字节的操作代码。

2.大小端:对一个2字节的short:0x1234
地址:0x01 0x02
大端:12 34 (先存高有效位,再存低有效位)
小端:34 12
(PS:十进制转十六进制可以用除法,不断除,但显然的,我们可以通过先把一个十进制数字存到内存,计算机会存为一个一个字节,这个时候我们只需按字节取出,
每个字节再细分为2个4位,通过移位得到每半个字节上的数字,然后把每个数字转为对应的十六进制字符(0-f),连起来就ok了,最后注意一下大小端就好了。
其实可以直接用%x输出十六进制结果的 =。=)
网络上字节流中的字节顺序是大端模式。
对多个字节存一个数据的整数,浮点数等,是受大小端影响的,但对字符串,则没有,它就是一个字节存一个字符。
PS:像utf-8这种使用字节序列表示一个字符的,受大小端影响吗?网上资料:字符串倒是没有大端小端的问题,但是字符可能会有大端小端的问题,
因为字符有可能不是一个字节,可能是2个(比如在Java里)或者4个字节(Python里有选项可以使用UTF16或者UTF32),C里也有wchar_t,可能是2个或者4个字节。

3.位向量表示有限集合:
如题,书上给出的例子是这样的:
a=[01101001]表示{0,3,5,6}
b=[01010101]表示{0,2,4,6}
最终a&b={01000001}={0,6}

谁能解释一下这8位二进制是如何表示集合的呢?
答案:第 i 位 为 1 表示 i 在 集合里。从右边数起,第一个是0位,对于a:第0位是1所以集合有0,第3位是1所以集合里有3...

4.布尔类型:
a^0 = a, a^1 = ~a

5.逻辑右移:左边补0,算术右移:左边补最高位。默认地,对无符号数,>>means逻辑右移,对有符号数,>>means算术右移,整数x>>1代表(x-1)/2

6.补码:在无符号数编码的基础上,最高位对应的数值加符号。计算机对有符号数一般使用补码编码
(注意,所谓无符号数编码,补码,反码,原码只是对一系列位的解释,对一个8位数,他们解释成不同的数值,这个解释的逆过程则是他们编码的过程)

7.有符号数和无符号数之间的转换,所谓转换,只是针对某个n位的数,换一种解释的方式,并不改变位值。
C语言在对同时包含有符号数和无符号数的表达式时,隐性把有符号数转成无符号数,再进行计算。
书中说:这个对标准的算术运算,结果和直观无多大差异,只对<和>有差异,但我的测试结果明显不对:

1    if (-1 < 0u)
2    {
3    int i = 0;
4    }
5    else
6    {
7    unsigned int a = 2147483648;// 0x80000000;
8    int c = -2147483647;//0x80000001
9    int j = c - a;//j结果是1
10    int k = 0;
11    }

第1行:-1转成无符号后变得很大,所以执行else,和直观不同
第7-9行:直观上j应该是一个很小的负数,担结果却是1
总结:尽量不要互转,除非你确定任何数值都没有问题。具体分析就是一步一步来,反正规则就是转换只改变解释方式,不改变位值,并且默认有符号转无符号。
在书中还举了个很容易犯的错误:

int sumElement(int a[], unsigned len)
{
int i = 0;
int ret = 0;
for (i = 0; i <= len - 1; ++i)//当len=0时,0u-1得到的无符号数很大,其实本质上是一个负的有符号数转为一个无符号数本身是有问题的!
{    //我想应该没有通用的解决方法,书中说的len改成int是一种解决方法显然不完全对:len很大至变成int是负数呢。
//第二种方法是i < len,这个是因为这个应用中i一定不是负数,
//我觉得应该谨记:避免负的有符号数转无符号数,或者它的逆过程。
ret += a[i];
}
return ret;
}

8.从小数据类型变成大数据类型,如short to int
无符号数左边补0,有符号数左边补最高位

9.同时改变大小和符号情况下,大小优先级高:
short x = -12345;
unsigned int y = x;//y = ff ff cf c7,等于y = (unsigned)((int)x);

10.两个无符号数相加是否溢出:s = x + y; 如果s < x则溢出,因为y < 2的w次方,(s < y也可以)
我突然想起以前做校招笔试题时,就有求2个整数的平均值的问题,(x+y)/2可能溢出,x/2+y/2是错的,正确方式:(x & y) + ((x ^ y) >> 1),因为:

  每个整数都可以分解成对应的二进制,然后2个二进制相加,如果某位相同,则相加后该位应该变为0或2,即位的值*2,如果不同,则保留为1,所以其平均数为:x&y + (x^y)>>1,前者是位值相同部分相加的平均数,后者是位值不同部分相加的平均数

11.两个补码相加:
x+y>=2的(w-1)次方:正溢出
x+y<-2的(w-1)次方:负溢出
在中间则正常
x和y都是负数,x+y>=0则会发生负溢出;当x和y都是正数,但x+y<0则发生正溢出
书中特别提到Tmin(比如4位则Tmin=-8),-8的负还是-8!所以对tmin要保持警惕,看下一条:

12.补码的非:x=Tmin则x的非是Tmin,因为Tmin+Tmin=0(这个是对计算机而言哈,不是直观的)
x>Tmin则x的非是-x.
在位级表示上,求x的非的方法:书中提了2种方法,其实原理是一样的,反正x的非就是0-x,想象一下位级的减法:
00000000   :向最高位借1 => 11111112
xxxxxxxx            xxxxxxxx
所以非最低位就是1-原值(0则是1,1则是0,即原值的非),而最低位则是2-原值,即1-原值后+1,这就是书中的方法一了。
书中的方法二也可以推出:找到xxxxxxxx最右边的那个1,左边是原值的非,右边不用借位都保持0,自己的位借了2所以为1.

13.无符号数的乘法和补码的乘法 都使用位数截断。
判断乘法是否溢出可以用下面代码判断:
int mul_ok(int x, int y)
{
int p = x*y;
return !x || p/x == y;
}
x!=0情况的数学证明方法思路是x*y= a + t*2的w次方, b = a/x=>b*x + r = a,当b=y时,t=r=0:不溢出;b!=y时,t!=0溢出
上面是补码乘法溢出判定,无符号数乘法溢出也类似

也可以用先用更大的数据类型存结果,然后转为小数据类型,看相不相等:int x,y; long long int p = x*y; int q = (int)p;if (p!=q)溢出。

14.乘以一个常数可以把常数换成2的幂的和,即把乘法换成移位和加法(减法)

15.先不看浮点数相关,先看第3章:程序的机器级表示,第3章主要讲程序的汇编级别代码

原文地址:https://www.cnblogs.com/Tearix/p/6917597.html