《深入理解计算机系统》学习二

前言😊

第二章,信息的表示和处理,将近有80多页内容,内容有些多,尽量将知识点覆盖完全。基本扫了一眼是对于数的运算,acm做题中,其实这类接触的很多。也就当复习来看。
并且markdown学会了如何变色<font color=RBG颜色参考>内容 </font>

学习下来,对于浮点数,无符号补码,原码的理解更深了。

信息存储😊

字节--最小的可寻址的内存单位。
虚拟内存--机器级程序将内存视为一个非常大的字节数组。
地址--内存的每个字节都由一个唯一的数字来标识。
虚拟地址空间--所有可能地址的集合。

🔴十六进制表示法

一个字节的值域 00H~FFH 00,000,000B~11,111,111B 0D~255D

🟠字数据大小

对于字长w位的机器而言,虚拟地址的范围0~2^w-1, 程序最多访问2^w个字节

现在基本都是64位机器(虚拟地址空间为16EB,大约1.84 * 10^19字节) 大多数的64位机器也可以运行为32位机器编译的程序(这是一种向后兼容)p27 C数据类型的典型大小(比如int,4个字节数)

这里要说明一个 unsigned (无符号),因为计算机一般默认有符号,负号会占一位,但在使用unsigned,就没有负号,会比有符号多一位(但不会有负数了)。

随着64位机器的普及,在将程序移植到新的机器上,许多隐藏的对字长的依赖性就会显示出来(如果32位上声明为int类型的程序对象能被用来储存一个指针(4字节数),但在64位的机器上会导致问题(8字节数))

🟡寻址和字节顺序

假设类型位int的变量x的地址为0x100,地址表达式&x的值为0x100

一个w位的整数,其位表示[Xw-1,Xw-2…X2,X1,X0],Xw-1为最高有效位,X0为最低有效位

字节顺序储存

1.最低有效位在最前面的方式,称为小端法(大多数Intel兼容机)

2.最高有效位在最前面的方式,称为大端法(IBM和Oracle的大多数机器)

字节顺序出现的情况

1.不同类型的机器之间通过网络传送二进制数据,小端法机器输给大端法机器,字里的字节成了反序的。(为了避免这种情况,将他内部转成网络标准,接收机器则将网络标准转换为它的内部表示,11章会提及)

2.阅读时候字节顺序(是左边读,还是右边起手读)这通常发生在检查机器级程序时,比如,反汇编得到的一条指令:

80483bd: 01 05 64 94 04 08     add %eax, 0x8049464

3.当编写规避正常的类型系统的程序时。在C语言中,可以通过使用强制类型转换(cast)或联合(union)来允许一种数据类型引用一个对象,二这种数据类型与创建这个对象时定义的数据类型不同。判断当前机器环境是大端法还是小端法:

int i=1; 
unsigned char * pointer = &i;
if(*pointer) printf("Little Endian"); 
else printf("Big Endian");

typedef 和 #define 区别 转载的出处,摘取其中的简洁内容,详细请看原文

1.执行时间

typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能。
而#define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。

2.功能不同

Typedef用来定义类型的别名,这些类型不只包含内部类型(int,char等),还包括自定义类型(如struct),可以起到使类型易于记忆的功能。有另外一个重要的用途,那就是定义机器无关的类型。
而#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

3.作用域不同

typedef有自己的作用域。(好比,外联函数定义的数据类型只能外联函数内用)
而#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。

4.对指针的操作(自我感觉最重要的一点)

二者修饰指针类型时,作用不同。

Typedef int * pint;
#define PINT int *
 
Const pint p;//p不可更改,p指向的内容可以更改,相当于 int * const p;
Const PINT p;//p可以更改,p指向的内容不能更改,相当于 const int *p;或 int const *p;

pint s1, s2; //s1和s2都是int型指针
PINT s3, s4; //相当于int * s3,s4;只有一个是指针。

🟢表示字符串

c语言中字符串被编码为一个以NULL字符结尾的字符数组。

🔵表示代码

p35页,一个sum函数,在Linux32,Window,Sun,Linux64生产的指令编码都是不同的。

不同的机器类型使用不同的且不兼容的指令和编码方式。

🟣布尔代数简介

最简单的布尔代数实在二元集合{0,1},表示false和true

🟤c语言中的位级运算

其实就是二进制上运算,状压dp做多了,直接熟悉,运算符号 ~ & | ^分别表示取反,并,或,异或 太熟悉了

c语言中的逻辑运算

感觉再上c语言,逻辑运算符 || && !分别表示或,和,非。和位级运算区别,有一个短路原理,&&或者|| 如果第一个确定了表达式的结果,那么逻辑运算符就不会对第二个参数求值。

c语言中的移位运算

线段树上用到这个特别多,1<<n 等效于 2^n ,n>>1 等效于 n/2 (位运算比加减乘除速度快)

整数表示😊

一种只能表示非负数
另一种能够表示负数,零和正数(p42图2-8的术语记得看看)

🔴整型数据类型

书中图2-9,2-10显示c数据类型的最大值和最小值,可以发现unsigned和signed的区别,同时取值范围负数比正数的范围大一。

🟠无符号数的编码

二进制转无符号,无符号转二进制时是双射,唯一性。(差不多就是二进制转十进制)

🟡补码编码

补码的定义,最高有效位的0,1是判断正负,1为负,0为正,跟二进制转十进制差不多,除了最高有效位Xw-1,如果是1就加上负的2^(w-1),也是双射,唯一性。

关于整数数据类型的取值范围和表示,Java标准是非常明确的。要求采用补码表示,单字节数据类型为byte。

p47反码和原码的公式。反码+1==补码,原码:最高有效位是符号位,用来确定剩下的位应该取负权or正权

🟢有符号数和无符号数之间的转换

c语言允许在各种不同的数字数据类型之间做强制类型转换

补码转换为无符号数

B2Uw(T2Bw(X))=T2Uw(X)=X+Xw-1 * 2^w
位Xw-1决定了x是否为负

无符号数转换为补码

U2Tw(U)=-Uw-1 * 2^w+U
位Uw-1决定了u是否大于Tmaxw=2^(w-1)-1

🔵* c语言中的有符号数与无符号数

这里提到了一个我觉得很重要的知识点,隐式转换 与之相对的就是显示转换(强制转换)

从小到大,可以隐式转换,数据类型将自动提升。

byte,short,char -->int -->long -->float -->double

比如,头文件string.h里面的sizeof()函数,返回值就是unsigned,举个例子

#include<bits/stdc++.h>
#define endl '\n'
#define IOS ios::sync_with_stdio(false)
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define ll long long
#define mod 1000000007
#define pb push_back
const double pi = acos(-1);
using namespace std;
const int N = 2e5+10;
void solve(){
    char a[5]="abcd";
    cout<<sizeof(a)<<endl;
    cout<<(unsigned int)(-1)<<endl;
    for(int i=-1;i<(int)sizeof(a);i++){
        cout<<"1"<<endl;
    }
    for(int i=-1;i<sizeof(a);i++){//这里其实就是发生了隐式转换i<sizeof(a)的i从int变成了unsigned int
        cout<<"2"<<endl;
    }
}
int main(){
    IOS;
    solve();
    return 0;
}
//请问最后有几个1,有几个2

运行完结果是

5
4294967295
1
1
1
1
1

-1<0U 等价于 4294967295U<0U (T2Uw(-1)=Umaxw)

🟣扩展一个数字的位表示

无符号数的零扩展

要将一个无符号数转换成一个更大的数据类型,我们只要简单地在表示的开头添加0。(这种运算称为零运算)

补码数的符号扩展

举例子吧,很容易懂,就是补码1110的话值为-8+4+2=-2 扩展一位的话加的是1,11110 -16+8+4+2=-2(神奇吧,二进制的特性),如果最高位加的是0,肯定不变
所以补码扩展了一个符号位 。

🟤截断数字

截断无符号数

对于任何i>=k 2^i mod 2^k=0;

截断补码数值

补码跟无符号有相似的属性,只不过要将最高位转换为符号位。

关于有符号数和无符号数的建议

许多无符号运算的细微特性,尤其是有符号数到无符号数的隐式转换,会导致错误或者漏洞。避免这类错误的一种方式就是不使用无符号数。

实际上,除了c以外的语言很少支持无符号整数,像java只支持有符号整数,并且要求以补码运算来实现。

整数运算😊

在acm中数据大小天天爆int,要么开ll,要么高精度,如果只开int范围,会导致最终答案会是一个很大的负数。

🔴无符号加法

无符号数加法

就正常的能理解的加法,不过这里有一个概念算术运算溢出,是指完整的整数结果不能放到数据类型的字长限制中去

检测无符号数加法中的溢出

用模数来判断有无溢出,这种模数加法形成了一种数学结构,称为阿贝尔群

它是可交换的和可结合的,它有一个单位元0,并且每个元素有一个加法逆元。(阿贝尔群就是整数环上的模)
这里就引出了无符号数取反。

无符号数求反

对于满足0<=x<2^w 的任意x,其w位的无符号逆元,当x=0时,无符号逆元等于0,当x>0时,无符号逆元等于2^w-x。

🟠补码加法

补码加法

-2w<=x,y<=2w-1范围:
正溢出:x+y-2^w ………… 2^(w-1) <=x+y
正常:x+y ……………………… -2^(w-1) <=x+y<2^(w-1)
负溢出:x+y+2^w ……………x+y < -2^(w-1)

两个数的w位补码之和与无符号之和有完全相同的位级表示(实际上,大多数计算机使用同样的机器指令来执行无符号或者有符号加法)

检测补码加法中的溢出

s=x+y

x>0,y>0 s<=0 正溢出,反过来讨论负溢出

🟡补码的非

补码的非

我知道的方法就是,补码按位取反+1,所以-8(1000)按位取反(0111)+1变成(1000)还是-8

🟢无符号乘法

无符号数乘法

正常乘法,也会有正溢出

🔵补码乘法

补码乘法

我学的时候看了视频,又看了几个博客,我觉得这个挺好,好的博客的地址

无符号和补码乘法的位级等价性

🟣乘以常数

乘以2的幂

等效于左移了一个数值

与2的幂相乘的【无符号乘法or补码乘法】

无论无符号还是补码,乘以2的幂都有可能会导致溢出。p70~p71 我再看看有些不理解

🟤除以2的幂

1.除以2的幂的无符号除法
2.除以2的幂的补码除法,向下舍入
3.除以2的幂的补码除法,向上舍入

关于整数运算的最后思考

其实“整数”运算实际上是一种模运算形式。

浮点数😊

浮点表示对形如V=x * 2^y的有理数进行编码
IEEE(电气和电子工程师协会)标准754--是一个仔细制订的表示浮点数及其运算的标准(这个大大的提高了科学应用程序在不同机器上的可移植性)

🔴二进制小数

0.1B=0.5D(1/2) 0.01B=0.25D(1/4) 0.001B=0.125D(1/8)

🟠🟡🟢IEEE浮点表示,数字示例,舍入

IEEE 浮点数标准是从逻辑上用三元组{S,E,M}来表示一个数 V 的,即 V=(-1)^S * M * 2^E

S M E
符号 尾数 阶码

符号(符号位) s(Sign)决定数是正数(s=0)还是负数(s=1),而对于数值 0 的符号位解释则作为特殊情况处理。
尾数(有效数字位) M(Significand)是二进制小数,它的取值范围为 1~2-ε,或者为 0~1-ε。它也被称为尾数位(Mantissa)、系数位(Coefficient),甚至还被称作“小数”。
阶码(指数位) E(Exponent)是 2 的幂(可能是负数),它的作用是对浮点数加权。
一个单独的符号位s直接编码符号s
k位的阶码字段exp=exp=ek-1…e1e0编码阶段E。
n位小数字段frac=fn-1…f1f0编码尾数M,但是编码出来的值也依赖于阶码字段的值是否等于0

情况1:规格化的值
情况2:非规格化的值
情况3:特殊值

这里我推一个博客感觉总结的比我好多了,系统的描述……

IEEE 754浮点数标准详解

🔵浮点运算

IEEE标准指定了一个简单的规则,来确定诸如加法和乘法这样的算术运算结果。
把浮点值x和y看成实数,而某个运算⊙定义在实数上,计算将产生round(x⊙y),这是对实际运算的精确结果进行舍入后的结果
还定义了一些使之更合理的规则,例如,定义了1/-0将产生-∞,定义了1/+0会产生+∞

浮点加法和浮点乘法,满足一些性质:

1.浮点加法满足交换率,但不满足结合律。

也不具备有结合性,这是缺少的最重要的群属性,但浮点加法满足了单调性属性。

2.浮点乘法也遵循通常乘法所具有许多属性,它可交换的,而且它的乘法单位元为1.0。

另一方面,由于可能发生溢出,或者舍入而失去精度,它不具备有可结合性。比如,float(1e20 * 1e20)* 1e-20的值为-∞,而1e20 *(1e20 * 1e-20)值为1e20

另外浮点乘法在加法上不具备分配性。比如,float 1e20 *(1e20 - 1e-20)值为0.0,(1e20 * 1e20)-(1e20 * 1e20)值为NaN(not a number)

🟣c语言中的浮点数

所有c语言版本提供了两种不同的浮点数据类型:float和double

向偶数舍入的舍入方式

🟤小结

对于这几天恍惚的学习课真的太多了,大致感觉第二章学习的差不多,跟别人的博客一对比(比如这篇讲浮点数的菊苣博客),很粗糙,还有网上视频比文字有趣多了,数字有点枯燥,而且不是很理解,老师板书一写,举一个特例突然明白。(这就可能是我每次上课听着简单,回去写作业写裂开的原因吧),第一章结尾的时候说每三四天更新,我觉得不是很现实。决定还是努力一周一更。🎈

update如果我在后面发现自己的错误or朋友指出错误,会立刻改正修改

posted @ 2021-03-11 20:18  ouluy  阅读(266)  评论(0编辑  收藏  举报