深入理解计算机系统_第二章_信息的表示和处理

深入,并且广泛
				-沉默犀牛

文章导读

这一章介绍了计算机中信息(即二值信号)的表示和处理。

  1. 信息存储
    1.1 十六进制表示法:介绍了十六进制的产生和十六进制、十进制、二进制之间的相互转换规律
    1.2 字数据大小:介绍了在不同机器和编译器中,数据类型数据在内存中所占的大小
    1.3 寻址和字节顺序 : 介绍了寻址方法和字节顺序(小端法、大端法),float与int的二进制表示有联系
    1.4 表示字符串:介绍了字符串的编码方式,以ASCII为例,说明了文本数据比二进制数据移植性好
    1.5 表示代码:介绍了在不同机器上产生的机器指令是不同的
    1.6 布尔代数简介:介绍了布尔代数及四种运算(~、&、|、^)
    1.7 C语言中的位级运算:介绍了布尔运算在C语言中的运用
    1.8 C语言中的逻辑运算:介绍了C语言中的逻辑运算及与位级运算的不同
    1.9 C语言中的移位运算:介绍了左移和右移(逻辑右移和算术右移)
  2. 整数表示
    2.1 整数数据类型:介绍了常见的整数数据类型
    2.2 无符号数的编码
    2.3 补码编码
    2.4 有符号数和无符号数之间的转换:同样的位模式应用不同的解释方法
    2.5 C语言中的有符号数与无符号数:着重提及了有符号数与无符号数混杂时的潜在危险
    2.6 扩展一个数字的位表示:介绍了无符号数的零扩展和补码数的符号扩展
    2.7 截断数字:介绍了无符号数的截断和补码数的截断
  3. 整数运算
    3.1 无符号加法:介绍了无符号数加法和溢出
    3.2 补码加法:介绍了补码数的加法和溢出
    3.3 补码的非 : 非是指加法逆元
    3.4 无符号乘法
    3.5 补码乘法:简单方法是转换为10禁止计算后转换为二进制后截断
    3.6 乘以常数:把常数转为 2的任意次幂的相互加减
    3.7 除以2的幂:分为无符号和有符号(补码)除法
    3.8 关于整数运算的最后思考:思考有限字长对于结果的影响
  4. 浮点数
    4.1 二进制小数
    4.2 IEEE浮点表示:分为规格化、非规格化和特殊值
    4.3 数字示例
    4.4 舍入:分为向偶数舍入、向零舍入、向下舍入、向上舍入
    4.5 浮点运算:加法可交换不可结合,乘法不具有结合性,不具有加法分配性

信息的表示和处理

现在计算机存储和处理的信息以二值信号表示。对于有10个手指的人类来说,使用十进制是很自然的事情,但是当构造存储和处理信息的机器是,二进制工作得更好。二值信号能够很容易得被表示、存储和传输,例如,可以表示为穿孔卡片上有洞或无洞、导线上的高电压或低电压,或者顺时针或者逆时针的磁场。对二值信号进行存储和执行计算的电子电路非常简单和可靠,制造商能够在一个单独的硅片上集成数百万甚至数十亿个这样的电路。

[说不定这个宇宙中有一个星球,在那上面的生物长了3个手指,那他们一定就是3进制的吧哈哈哈哈哈]
[想起来一句名言,世界上有十种人,一种懂二进制,一种不懂二进制,哈哈哈哈哈哈哈哈 -2019/5/6]

孤立地讲,单个的位不是非常有用。然而,当把位组合在一起,再加上某种解释,即赋予不同的可能位模式以含义,我们就能够表示任何有限集合的元素。比如,使用一个二进制数字系统,我们能够用位组来编码非负数。通过使用标准的字符码,我们能够对文档中的字母和符号进行编码。

我们研究三种最重要的数字表示。无符号(unsigned)编码基于传统的二进制表示法,表示大于或者等于零的数字。补码(two‘s-complement)编码是表示有符号整数的最常见的方式,有符号整数就是可以为正或者为负的数字。浮点数(floating-point)编码是表示实数的科学计数法的以2为基数的版本。计算机用这些不同的表示方法实现算术运算,例如加法和乘法,类似于对应的整数和实数运算。

计算机的表示法是用有限数量的位来对一个数字编码,因此,当结果太大以至于不能表示时,某些运算就会溢出(overflow)。溢出会导致某些令人吃惊的后果。例如,在今天的大多数计算机上(使用32位来表示数据类型int),计算表达式200300400*500会得出-884901888。这违背了整数运算的特性,计算一组正数的乘积不应产生一个负的结果。

另一方面,整数的计算机运算满足人们所熟知的真正整数运算的许多特性。例如,利用乘法的结合律和交换律。虽然计算机可能没有产生期望的结果(人类期望),但是至少它是一致的!

浮点运算有完全不同的数学属性。虽然溢出会产生特殊的值正无穷,但是一组正数的乘积总是正的。由于表示的精度有限,浮点运算是不可结合的。例如,在大多数机器上,C表达式(3.14 + 1e20)- 1e20求得的值会是0.0,而3.14 +(1e20 - 1e20)求得的值会是3.14。整数运算和浮点运算会有不同的数学属性是因为它们处理数字表示有限的方式不同——整数的表示虽然只能编码一个相对较小的数字范围,但是这种表示是精确的;而浮点数虽然可以编码较大的数值范围,但是这种表示只是近似的。

通过研究数字的实际表示,我们能够了解可以表示的值的范围和不同的算术运算属性。大量的计算机的安全漏洞都是由于计算机算术运算的微妙细节引发的。这导致了众多的黑客企图利用他们能找到的任何漏洞,不经过授权就进入他人的系统。

[学习微机的时候就觉得数字的存储和运算很麻烦,终于还是逃不脱啊。。。]
[今天再看也还是觉得有点烦啊。。。耐住性子啊。。。 -2019/5/6]

信息存储

大多数计算机使用8位的块,或者字节(byte),作为最小的可寻址的内存单位,而不是访问内存中单独的位[把8个位想成一个班级,校领导能找的最小单位是班级]。机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。内存的每个字节都由一个唯一的数字来标识,称为它的地址,所有可能地址的集合就称为虚拟地址空间。顾名思义,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际的实现是将动态随机访问存储器、闪存、磁盘存储器、特殊硬件和操作系统软件结合起来,为程序提供一个看上去统一的字节数组。

在接下来的几章中,我们将讲述编译器和运行时系统是如何将存储空间划分为更可管理的单元,来存放不同的程序对象(program object),即程序数据、指令和控制信息。可以用各种机制来分配和管理程序不同部分的存储。这种管理完全是在虚拟地址空间里完成的。例如,C语言中一个指针的值(无论它指向一个整数、一个结构或是某个其他程序对象)都是某个存储块的第一个字节的虚拟地址。C编译器还把每个指针和类型信息联系起来,这样就可以根据指针值的类型,生成不同的机器级代码来访问存储在指针所指向位置处的值。尽管C编译器维护着这个类型信息,但是它生成的实际机器级程序并不包含关于数据类型的信息。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。
[这样就更好的理解函数指针了,反正函数也只不过是一个字节序列,那大可有一个指针指向它嘛 -2019/5/6]

指针是C语言的一个重要特性。它提供了引用数据结构(包括数组)的元素的机制。与变量类似,指针也有两个方面:值和类型。它的值表示某个对象的位置,而它的类型表示那个位置上所存储对象的类型(比如整数或者浮点数)。

十六进制表示法

一个字节由8位组成。在二进制表示法中,它的值域是00000000 ~ 11111111。如果看成十进制数,它的值域就是0 ~ 255。两种符号表示法对于描述位模式来说都不是非常方便。二进制表示法太冗长,而十进制表示法与位模式的互相转化很麻烦。替代的方法是,以16为基数,或者叫做十六进制(hexadecimal)数,来表示位模式。十六进制(简写hex)使用数字“0” ~ “9” 以及字符“A”~“F”来表示16个可能的值[马路上的红灯超时间超过100秒后就会用A ~ F来表示]。用十六进制书写,一个字节的值域为00~FF。

在C语言中,以0x或0X开头的数字常量被认为是十六进制的值。字符A~F既可以是大写,也可以是小写,也可以是大小写混合。

记住十六进制与十进制转化的窍门就是,记住十六进制A、C、E相应的十进制数,这样对于B、D、F的十进制数只要加1即可。

比如,给你一个数字0x173A4C。可以通过展开每个十六进制数字,将它转换为二进制格式,如下图所示:
在这里插入图片描述
反过来,如果给你一个二进制数,可以通过首先把它分为每4位一组来转化为十六进制。不过要注意,如果位总数不是4的倍数,最左边的一组要前面补0,然后再将每个4位组转换为相应的十六进制数字:
在这里插入图片描述
[这个内容是比较简单的,进制转换是程序员的基本功吧,不过才知道原来十六进制的提出是为了简便的。]

十进制和十六进制表示之间的转换需要使用乘法或者除法来处理一般情况,例如,十进制为314156,则如下图:在这里插入图片描述
所以十六进制表示为 0x4CB2C。
如果十六进制为0x7AF,则十进制数为7×16的平方 + 10×16 + 15 = 1967

字数据大小

每台计算机都有一个字长(word size),指明指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址的最大大小。也就是说,对于一个字长为w位的机器而言,虚拟地址的范围为0~(2的w地方 )- 1,程序最多访问(2的w次方)个字节。

[看来我们的系统分为32位64位的差别就在于此,所以同样的机器既可以装32位的系统,也可以装64位的系统,因为这个位数决定了虚拟地址的大小,跟实际你的机器的物理内存大小没关系。但是大多数软件也分了32位版本64位版本,而32位64位的虚拟地址大小不同,所以如果装错了会出现蓝屏等现象,我想一定是64位的软件装在了32位的系统上,请求了超过4GB(2的32次方)-1)这个大小的地址,就蓝屏了。]

最近这些年,出现了大规模的从32位字长机器到64位字长机器的迁移。32位字长限制虚拟地址空间为4GB,扩展到64位字长使得虚拟地址空间为16EB。

大多数64位机器也可以运行(为32位机器编译)的程序,这是一种后向兼容。我们将程序称为“32位程序”或者“64位程序”时,区别在于该程序是如何编译的,而不是其运行的机器类型。

计算机和编译器支持多种不同方式编码的数字格式,如不同长度的整数和浮点数。比如,许多机器都有处理单个字节的指令,也有处理表示为2字节、4字节或者8字节整数的指令,还有些指令支持表示为4字节和8字节的浮点数。

C语言支持整数和浮点数的多种数据格式。有些数据类型的确切字节数依赖于程序是如何被编译。
在这里插入图片描述
为了避免由于依赖“典型”大小和不同编译器设置带来的奇怪行为,ISO C99引入了一类数据类型,其数据大小是固定的,不随编译器和机器设置而变化。其中就有数据类型int32_tint64_t,它们分别是4个字节和8个字节。使用确定大小的整数类型是程序员准确控制数据表示的最佳途径。

[怪不得我在很多程序中看到int32_tint64_t这两种定义方法,原来是为了固定数据大小。]

程序员应该力图使它们的程序在不同的机器和编译器上可移植。可移植性的一个方面就是使程序对不同数据类型确切大小不敏感。如果不关注这个问题,就有可能出现错误,比如,许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针。这在大多数32位的机器上能正常工作,但是在一台64位的机器上却会导致问题。

[这一点我很疑惑,int类型在32位机器为4个字节,作为指针的话最大地址为4GB,但是64位机器的寻址地址最大为16EB,是大于4GB的,为啥会导致问题呢??????]
[这有啥疑惑的。。。正因为int类型在32位机器和64位机器都是4个字节,所以int类型指针只能存最大为4GB的指针,而64位机器需要存储16EB的指针,超过了int类型指针的表示范围,所以会有问题啊。 -2019/5/6]

寻址和字节顺序

对于跨越多字节的程序对象,我们必须建立两个规则:这个对象的地址是什么,以及在内存中如何排列这些字节。在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。例如,假设一个类型为int的变量x的地址为0x100,也就是说,地址表达式&x的值为0x100。那么(假设数据类型int为32位表示)x的4个字节将被存储在内存的0x100、0x101、0x102、0x103位置。

排列表示一个对象的字节有两个通用的规则。即小端法(little endian)大端法(big endian)小端法是指机器选择在内存中按照从最低有效字节到最高有效字节的顺序存储对象;大端法是指机器选择在内存中按照从最高有效字节到最低有效字节的顺去存储对象。

假设变量x的类型为int,位于地址0x100处,它的十六进制值为0x01234567。
在这里插入图片描述
注意,在字0x01234567中,高位字节的十六进制值是0x01,而低位字节是0x67。在哪种字节顺序是合适的这个问题上,人们表现得非常情绪化。但其实只要选择了一种规则并且始终如一地坚持,对哪种字节排序的选择都是任意的。

对于大多数应用程序员来说,其机器所使用的字节顺序是完全不可见的。无论为哪种类型的机器所编译的程序都会得到同样的结果。不过有的时候,字节顺序会成为问题。

  1. 在不同类型的机器之间通过网络传送二进制数据时,小端法机器产生的数据发送到大端法机器时(或者反过来),字里的字节成了反序的。为了避免这类问题,网络应用程序的代码必须遵守已经建立的关于字节顺序的规则,以确保发送方机器将它内部表示转换成网络标准,而接收方机器则将网络标准转换为它的内部表示。
  2. 阅读表示整数数据的字节序列时字节顺序也很重要。这通常发生在检查机器级程序时。就是在小端法机器生成的机器级程序表示中,书写字节序列的方式是最低位字节在左边,最高位在右边,这正好与通常人类书写数字时最高有效位在左边,最低有效位在右边的方式相反。
  3. 编写规避正常的类型系统的程序时字节顺序也会很重要,在C语言中,可以通过使用强制转换类型(cast)联合(union)来允许以一种数据类型引用一个对象,而这种数据类型与创建这个对象时定义的数据类型不同。

这里展示一段C代码,及其测试程序和结果:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
参数12345的十六进制表示为0x00003039。

  1. 对于int类型的数据,除了字节顺序以外,我们在所有机器上得到了相同的结果。(我们可以看到在Linux 32、windows和Linux 64上,最低有效字节0x39最先输出,这说明它们是小端法机器;而在Sun上最后输出,这说明Sun是大端法机器。)
  2. float数据的字节,除了字节顺序以外,也都是相同的。
  3. 指针值却是完全不同的。不同的机器/OS配置使用不同的存储分配规则,Linux 32、Windows和Sun的机器使用4字节地址,而Linux 64使用8字节地址。

可以观察到,尽管float和int数据都是对数值12345编码,但是它们有截然不同的字节模型:int为0x00003039,而float为0x4640E400。如果我们将这些十六进制模式扩展为二进制形式,并且适当地将它们移位,就会发现一个有13个相匹配的位的序列:
在这里插入图片描述
这不是巧合,以后研究浮点数格式的时候,

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值