第二章 信息的表示和处理

信息的表示和处理

现在计算机存储和处理的信息以二值信号表示。使用十进制是很自然的事情,但是当构造存储和处理信息的机器是,二进制工作得更好。二值信号能够很容易得被表示、存储和传输。对二值信号进行存储和执行计算的电子电路非常简单和可靠,制造商能够在一个单独的硅片上集成数百万甚至数十亿个这样的电路。

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

我们研究三种最重要的数字表示。无符号(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。整数运算和浮点运算会有不同的数学属性是因为它们处理数字表示有限的方式不同——整数的表示虽然只能编码一个相对较小的数字范围,但是这种表示是精确的;而浮点数虽然可以编码较大的数值范围,但是这种表示只是近似的。

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

2.1 信息存储

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

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

2.1.1 十六进制表示法

一个字节由8位组成。在二进制表示法中,它的值域是00000000 ~ 11111111。如果看成十进制数,它的值域就是0 ~ 255。两种符号表示法对于描述位模式来说都不是非常方便。二进制表示法太冗长,而十进制表示法与位模式的互相转化很麻烦。替代的方法是,以16为基数,或者叫做十六进制(hexadecimal)数,来表示位模式。十六进制(简写hex)使用数字“0” ~ “9” 以及字符“A”~“F”来表示16个可能的值。用十六进制书写,一个字节的值域为00~FF。
在这里插入图片描述
在C语言中,以0x或0X开头的数字常量被认为是十六进制的值。字符A~F既可以是大写,也可以是小写,也可以是大小写混合。

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

比如,给你一个数字0x173A4C。可以通过展开每个十六进制数字,将它转换为二进制格式,如下图所示:
在这里插入图片描述
反过来,如果给你一个二进制数,可以通过首先把它分为每4位一组来转化为十六进制。**注意:**如果位总数不是4的倍数,最左边的一组要前面补0,然后再将每个4位组转换为相应的十六进制数字:
在这里插入图片描述
十进制和十六进制表示之间的转换需要使用乘法或者除法来处理一般情况,例如,十进制为314156,
在这里插入图片描述
所以十六进制表示为 0x4CB2C。
如果十六进制为0x7AF,则十进制数为7×16^2 + 10×16 + 15 = 1967

2.1.2 字数据大小

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

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

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

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

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

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

2.1.3 寻址和字节顺序

对于跨越多字节的程序对象,我们必须建立两个规则:这个对象的地址是什么,以及在内存中如何排列这些字节。在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。例如,假设一个类型为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代码,及其测试程序和结果:

#include <stdio.h>

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, size_t len) {
   
	size_t i;
	for (i = 0;i < len;i++)
		printf(" %.2x",start[i]);
	printf("\n");
}

void show_int(int x) {
   
	show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x) {
   
	show_bytes((byte_pointer) &x, sizeof(float));
}

void show_pointer(void *x) {
   
	show_bytes((byte_pointer) &x, sizeof(void *));
}
void test_show_bytes(int val) {
   
	int ival = val;
	float fval = (float) ival;
	int *pval = &ival;
	show_int
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值