1 2 3 4

CSAPP(第三版)第二章信息的表示和处理学习笔记

信息存储

大部分计算机使用8位的块,(或者字节)来作为最小的可寻址的内存单位,而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组,称之为虚拟内存(virtual memory)。内存的每一个字节由唯一的数字来标识,称为它的地址(address),所有可能地址的集合称之为虚拟地址空间(virtual address space)。

十六进制表示法

一个字节由8位组成,用二进制表示就是 000000002-111111112 ,用十进制表示是010-25610,用十六进制表示是0016-FF16

字数据大小

每台计算机都有一个字长(word size),指明指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为w位的机器而言,虚拟地址的范围为0~2w-1。
enter description here
程序员应该力图使他们的程序在不同的计算机和编译器上可移植,可移植的其中一个方面就是使程序对不同的数据类型的确切大小不那么敏感

寻址和字节顺序

对于跨多个字节的程序对象,我们必须建立两个规则:这个对象的地址是什么,以及在内存中如何排列这些字节。

小端法
最低有效字节在最前面的方式

大端法
最高有效字节在最前面的方式

假设变量x的类型为int,文娱地址0x100处,它的十六进制值为0x01234567。地址范围0x100~0x103的字节依赖于机器的类型::
enter description here

几种机器所使用的字节顺序会成为问题的情况:

  • 在不同类型的机器之间通过网络传送二进制数据时,一个常见的问题是当小端法机器产生的数据被发送到大端法机器或者反过来时,接受程序会发现,字里的字节成了反序的。
  • 当阅读表示整数数据的字节序列时字节顺序也很重要。
  • 当编写规避正常的类型系统的程序时。

表示字符串

C语言中的字符串被编码为一个以null(其值为0)字符结尾的字符数组。

表示代码

不同的机器类型使用的且不兼容的指令和编码方式。即使是完全一样的进程,运行在不用的操作系统上也会有不同的编码规则,因此二进制代码是不兼容的。二进制代码很少能在不同机器和操作系统组合之间移植。

布尔代数简介

二进制值是计算机编码,存储和操作信息的核心,所以围绕数值0和1的研究已经演化除了丰富的数学知识体系,最简单的布尔代数是在二元集合{0,1}基础上的定义。

C语言中的位级运算

包括按位与、按位或、异或运算,位运算的一个常见应用就是实现掩码运算(从一个字中选出一个组位)

C语言中的逻辑运算

包括逻辑或||、逻辑与&&、逻辑非!
逻辑运算表达式中,第一个参数能够确定表达式的结果的时候,逻辑运算表达式就不会计算第二个参数的值

C语言中的移位运算

向左移位运算右端补0
向右移位运算包含两种形式:逻辑移位(左端补0)和算数移位(左端补最高有效位)

整数表示

下图列出了引入的数学术语,用于精确定义和描述计算机如何编码和操作的整数。
enter description here

整型数据类型

enter description here
enter description here
enter description here

无符号数的编码

假设有一个整数数据类型有w位。我们可以将位向量写成\({ \vec {x} }\),表示整个向量,或者写成[xw-1,xw-2,\({\cdot \cdot \cdot}\),x0],表示向量中的每一位。把\({ \vec {x} }\)看做一个二进制数表示的数,就获得了\({ \vec {x} }\)的无符号表示。在这个编码中,每个位xi都取值为0或者1,后一种取值意味着数值2i应为数字值的一部分。
我们用一个函数B2Uw(Binary to Unsigned的缩写,长度为w)来表示
原理:无符号数编码的定义
对象量\(\vec {x} = [x_{w-1},x_{w-2},\cdot\cdot\cdot,x_{0}]\)

\[B2U_{w} =\sum_{i=0}^{w-1}x_{i}2^{i} \]

补码编码

对于许多应用,我们还希望表示负数值。最常见的有符号数的计算机表示方式就是补码形式。在这个定义中,将字的最高有效位解释为负权(negative weight)。我们用函数B2Tw(Binary to Two,s-complement的缩写,长度为w)来表示:
原理:补码编码的定义
对象量\(\vec {x} = [x_{w-1},x_{w-2},\cdot\cdot\cdot,x_{0}]\)

\[B2T_{w} =-x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}x_{i}2^{i} \]

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

C 语言允许在各种不同的数字数据类型之间做强制类型转换。
原理:补码转换为无符号数
对于满足\(TMin_{w}{\leq}x{\leq}TMax_{w}\)

\[T2U_{w}(x)= \begin{cases} x + 2^{w},\quad x<0\\ x, \quad x\geq0 \end{cases} \]

推导:补码转换为无符号数

\[B2U_{w}(T2B_{w}(x))=T2U_{w}(x)=x + x_{w-1}2^{w} \]

原理:无符号数转换为补码
对于满足\(0{\geq}x{\leq}UMax_{w}\)

\[T2U_{w}(x)= \begin{cases} u,\quad u\leq TMax_{w}\\ u-2^{w}, \quad u>TMax_{w} \end{cases} \]

推导:无符号数转换为补码

\[U2T_{w}(u)=-u_{w-1}2^{w}+u \]

C语言中的有符号数与无符号数

C 语言允许无符号数和有符号数之间的转换。转换的原则是底层的位表示保持不变

扩展一个数字的位表示

零扩展: 将一个无符号数转换为一个更大的数据类型,在开头加0
符号扩展: 将一个二进制补码转化为一个更大的数据类型,在开头加最高有效位

截断数字

截断一个数字可能会改变它的值——溢出的一种形式

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

有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。而这些非直观的特性经常导致程序错误,并且这种包含隐式强制类型转换细微差别的错误很难被发现。因为这种强制类型转换是在代码中没有明确指示的情况下发生的,程序员经常忽视了它的影响。

避免这类错误的一种方法就是绝不使用无符号数。实际上,除了 C 以外,很少有语言支持无符号整数。

整数运算

无符号加法

每个数都能表示为 w 位无符号数字。然而,如果计算他们的和,我们就有一个可能的范围\(0\leq x+y\leq2^{w+1}-2\)。表示这个和可能需要\(w+1\)位。
让我们为参数\(x\)\(y\)定义运算\(+_{w}^{u}\),其中\(0\leq x, y < 2^{w}\),该操作是把整数和\(x+y\)阶段为w位得到的结果,再看作是一个无符号数。
原理:无符号数加法
对满足\(0\leq x,y<2^{w}\)\(x\)\(y\)有:

\[x+_{w}^{u}y= \begin{cases} x+y,\quad x+y\leq 2^{w}\\ x+y-2^{w}, \quad 2^{w}\leq x+y<2^{w+1} \end{cases} \]

enter description here

补码加法

对于补码加法,我们必须确定结果太大(为正)或者太小(为负)时,应该做些什么。
enter description here

补码的非

原理:补码的非
对于满足\(TMin_{w}\leq x\leq TMax_{w}\)\(x\),其补码的非\(-_{w}^{t}x\)由下式给出

\[-_{w}^{t}x= \begin{cases} TMin_{w},\quad x=Min_{w}\\ -x, \quad x>TMin_{w} \end{cases} \]

推导:补码的非最小值
\(TMin_{w}=-2^{w-1}\)

无符号乘法

原理:无符号数乘法
对于满足\(0\leq x\),\(y\leq UMax_{w}\)\(x\)\(y\):

\[x *_{w}^uy=(x \cdot y)mod2^{w} \]

补码乘法

原理:补码乘法
对于满足\(TMin_{w}\leq x\),\(y \leq TMax_{w}\)\(x\)\(y\)有:

\[x*_{w}^{t}y = U2T_{w}((x\cdot y)mod2^{w}) \]

乘以常数

在大多数机器上,整数乘法指令相当慢,需要 10 个或者更多的时钟周期,然而其他整数运算(例如加法、减法、位级运算和移位)只需要 1 个时钟周期。因此,编译器使用了一项重要的优化,试着用移位和加法运算的组合来代替乘以常数因子的乘法。
原理:乘以2的幂
\(x\)为位模式\([x_{w-1},x_{w-2},\cdot\cdot\cdot,x_{0}]\)表示的无符号整数。name,对于任何\(k\geq 0\),我们都认为\([x_{w-1},x_{w-2},\cdot\cdot\cdot,x_{0},0\cdot\cdot\cdot,0]\)给出了\(x2^{k}\)\(w+k\)的无符号表示,这里右边增加了\(k\)个0。
因此,比如当\(w=4\)时,\(11\)可以呗表示\([1011]\)\(k=2\)时将其左移得到6位向量\([101100]\),即可编码为无符号数\(11 \cdot 4 = 44\)
推导:乘以2的幂

\[B2U_{w+k}([x_{w-1},x_{w-2},\cdot\cdot\cdot,x_{0},0\cdot\cdot\cdot,0]) = \sum_{i=0}^{w-1}x_{i}2^{i+k} =[\sum_{i=0}^{w-1}x_{i}2^{i}]\cdot2^{k} = x2^{k} \]

除以2的幂

在大多数机器上,整数除法要比整数乘法更慢——需要 30 个或者更多的周期。除以 2 的幂也可以用移位运算来实现,只不过我们用的是右移,而不是左移。无符号和补码数分别使用逻辑移位和算术移位来达到目的。

关于整数运算的最后思考

正如我们看到的,计算机执行的“整数”运算实际上是一种模运算形式。表示数字的有限字长限制了可能的值的取值范围,结果运算可能溢出。

浮点数

二进制小数

enter description here

IEEE浮点表示

IEEE浮点标准用 \(V = (-1)^s * M * 2^E\) 的形式来表示一个数:

  • 符号(sign) \(s\)决定这个数是负数\((s=1)\)还是正数\((s=0)\),对于数值 0 的符号位解释作为特殊情况处理。
  • 尾数(significand) \(M\) 是一个二进制小数,它的范围是 \(1 \sim 2 - \varepsilon\),或者是 \(0 \sim 1 - \varepsilon\)
  • 阶码(exponent) E 的作用是对浮点数加权,这个权重是 2 的 E 次幂(可能是负数)

将浮点数的位表示划分为三个字段,分别对这些值进行编码:

  • 一个单独的符号位 \(s\) 直接编码符号 \(s\)
  • \(k\) 位的阶码字段 \(exp = e_{k-1}\cdot\cdot\cdot e_{1}e_{0}\) 编码阶码 E。
  • \(n\) 位小数字段 \(frac = f_{n-1}\cdot\cdot\cdot f_{1}f_{0}\) 编码尾数 M,但是编码出来的值也依赖于阶码字段的值是否等于 0。
    在单精度浮点格式(C余元中的float)中,s,exp和frac字段分别为1位,\(k=8\)位和\(n=23\)位,得到一个32位的表示。在双精度浮点格式(C语言中的double)中,s,exp和frac字段分别为1位,\(k=1\)位和\(n=52\)位,得到一个64位的表示。
    enter description here
    给定位表示,根据exp的值,被编码的值可以分成三种不同的情况(最后一种有两个变种)
    enter description here

舍入

因为表示方法限制类浮点数的范围和精度,浮点运算只能近似地表示实数运算。因此,对于值 x,我们一般想用一种系统的方法,能够找到“最接近的”匹配值,这就是舍入运算的任务。

常见的舍入方式有:向偶数舍入、向零舍入、向下舍入、向上舍入
enter description here

浮点运算

浮点加法不具有结合性。浮点乘法在加法上不具备分配性。对于科学计算程序员和编译器编写者来说,这是很严重的问题,即使为了在三维空间中确定两条线是否交叉而写代码这样看上去很简单的任务,也可能成为一个很大的挑战。

C语言中的浮点数

所有的C语言版本提供了两种不同的浮点数据类型:float 和 double。在 int、float 和 double 格式之间进行强制类型转换时,程序改变数值和位模式的原则如下(假设 int 是 32 位的):

  • 从 int 转换成 float,不会溢出,可能被舍入。

  • 从 int 或 float 转换成 double,能够保留精确的数值。

  • 从 double 转换成 float,可能溢出成为正无穷或负无穷,也可能被舍入。

  • 从 float 或者 double 转换成 int,值会向零舍入。例如 1.999 将被转换成 1。

posted @ 2022-03-24 16:11  无序  阅读(94)  评论(0编辑  收藏  举报