这章开始,我们必须要先明确一个概念:学习计算机的都知道,现在计算机存储和处理的信息以 二进制 数字表示,或者称为 位。最主要的原因在于 二进制能够更容易的杯表示,存储和传输。
单个的位不是非常有用,然而,当把位组合在一起,再加上某种解释,即赋予不同的可能位模式以含义,我们就能够表示任何有限集合的元素。
三种最重要的数字表示:
- 无符号编码(unsigned):基于传统的二进制表示法——表示 大于 或者 等于 零的数字。
- 补码编码(two's complement):表示有符号整数的最常见的方式。
- 浮点数编码(floating point):表示实数的科学记数法的以 2 为基数的版本。
计算机的表示法是**用有限数量的位来对一个数字编码。**当结果太大以至于不能够表示时,某些运算就会溢出。
C 语言发展史:
贝尔实验室 -> 美国国家标准学会 ANSI C 标准 -> 国际标准化组织 ISO C 90 -> ISO C 99 -> ISO C11
2.1 信息存储
大多数计算机使用 8 位的块,或者 字节,作为最小的可寻址的内存单位,而不是访问内存中单独的位。机械级程序将内存视为一个非常大的字节数组,称为虚拟内存。内存的每个字节都由一个唯一的数字来标识,称为他的 地址,所有可能地址的集合称为虚拟地址空间。这个虚拟地址空间只是一个展现给机器级程序的概念性映像。他们之间管如图
十六进制表示法
说白了其实就是使用十六进制去表示二进制,即二进制转十六进制。
转化其实十分简单,如下:
字数据的大小
每台计算机都有一个 字长,指明指针数据的标称大小。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说:对于一个字长为 w 位的机器而言,虚拟地址的范围为 0~2w-1,程序最多访问 2w 个字节。
近年最常见的就是 32 位字长机器到 64 位字长机器的迁移。
- 32 位字长限制虚拟地址空间为 4 千兆字节(即 4GB),刚刚超过 4 × 109 字节
- 64 位字长限制虚拟地址空间位 16EB,大约是 1.86 × 1019 字节
我们将程序称为 “32 位程序“ 或 ”64 位程序“ 时,区别在于该程序是如何编译的,而不是其运行的机器类型:
# 32 位程序
linux > gcc -m32 prog.c
# 64 位程序
linux > gcc -m64 prog.c
C 语言支持整数和浮点数的多种数据格式,如下图(重要):
大部分数据类型都编码为有符号数值,除非有前缀关键字 unsigned 或对确定大小的数据类型使用了特定的无符号声明。数据类型 char 是一个例外。
寻址和字节顺序
对于跨越多字节的程序对象(即程序数据、指令和控制信息),需要建立两个规则:
- 这个对象的地址是什么
- 如何在内存中排列到这些字节
**多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。**例如,一个 int 类型(假设占用 4 个字节)的变量 x 的地址为 0x100,即地址表达式 &x 为 0x100.那么 x 的字节字节被存储在内存的 0x100、0x101、0x102 和 0x103 位置。
对于字节的排列方式有两种,某些机器选择在内存中按照从最低有效字节到最高有效字节的顺序存储对象,而另外一些机器则按照从最高的有效字节到最低有效字节的顺序存储。即
- 前一种规则——最低有效字节在最前面的方式,称为 小端法。
- 后一种规则——最高有效字节在最前面的方式,称为 大端法。
假设变量 x 的类型位 int,位于地址 0x100 处,他的十六进制值为 0x01234567,地址范围 0x100~0x103的字节顺序依赖于机器的类型:
字节顺序对于大多数应用程序员来说是不可见的,无论使用哪种类型的机器所编译的程序都会得到同样的结果。不过有时候,字节顺序会成为问题:
- 在不同类型的机器之间通过网络传送二进制数据时,一个常见的问题是当小端法机器产生的数据被发送到大端法机器或者反过来时,接受程序会发现,字里的字节成了反序的。
- 当阅读表示整数数据的字节序列时字节顺序也很重要。
- 当编写规避正餐的类型系统的程序时。
可以通过
man ascii
来得到一张 ASCII 字符码的表。manjaro 18 生成结果。
字符串、代码、布尔代数
C 语言中字符串被编码为一个以 null(其值为 0) 字符结尾的字符数组。在使用 ASCII 码来作为字符码的任何系统上都将得到相同的结果,与字节顺序何字大小规则无关。因此,文本数据比二进制数据具有更强的平台独立性。
二进制代码是不兼容的,二进制代码很少能够在不同机器和操作系统组合之间移植。
计算机系统的一个基本概念就是,从机器的角度来看,程序仅仅只是字节序列。机器没有关于原始源程序的任何信息,除了可能有些用来帮助调试的辅助表以外。
最简单的布尔代数是在二元集合(0, 1)基础上定义的:
我们引入一个位向量的概念,**位向量就是固定长度为 w、由 0 和 1 组成的串。**我们将布尔运算扩展到位向量上
位级运算、逻辑运算、移位运算
C 语言支持按位布尔运算,我们在布尔运算中使用的符号就是 C 语言所使用的:
- | OR 或
- & AND 与
- ~ NOT 取反
- ^ EXCLUSIVE OP 异或
以下是一些对 char 数据类型表达式求值的例子:
位级运算一个常见用法就是实现掩码运算,这里的掩码是一个位模式,表示从一个字中选出的位的集合。
C 语言还提供了一组逻辑运算符
- || —— 逻辑或
- && —— 逻辑与
- ! —— 逻辑非
还有一组 移位运算,向左或者向右移动位模式,对于右移,又有 逻辑右移 和 算术右移,示例如下:
几乎所有的编译器/机器组合都对有符号数使用算术右移,且许多程序员也都假设机器会使用这种右移。对于无符号数,右移必须是逻辑的。
对于 Java。 x >> k 会将 x 算术右移 k 个位置,而 x >>> k 会对 x 做逻辑右移。
对于一个由 w 位组成的数据类型,当 k 大于等于 w 位时。位移量就是通过计算 k mod w 得到的。
2.2 整数表示
我们描述用位来编码整数的两种不同的方式
- 只能表示非负数
- 能够表示负数、零和正数
引入以下数学术语
整型数据类型
C 语言支持多种整型数据类型——表示有限范围的整数。
一个特点——负数的范围比整数的范围大 1。
C 语言定义了每种数据类型必须能够表示的最小取值范围。
C 和 C++ 都支持有符号(默认)和无符号数,Java 只支持有符号数。
无符号数的编码
例如:
w 位能够表示的范围:
- 最小值用位向量 [000…0] 表示,也就是整数值 0。
- 最大值用位向量 [111…1] 表示,也就是整数值
。
因此,函数 B2Uw 能够被定义成一个映射 B2Uw。
补码编码
对于补码编码,它可以表示负数值,将字的最高有效位解释位 负权,用函数 B2Tw 表示。
w 位能够表示的范围:
-
最小值用位向量 [100…0] 表示,也就是整数值
。
-
最大值用位向量 [011…1] 表示,也就是整数值
。
下图展示了针对不同字长,几个重要数字的位模式何数值:
注意一下几点:
- 补码的范围是不对称的: |TMin| = |TMax| + 1,也就是说,TMin 没有与之对应的正数。
- 最大的无符号数刚好比补码的最大值的两倍大一点:UMaxw = 2TMaxw + 1。补码表示中所有表示负数的位模式在无符号表示中都变成了正数。
有符号数和无符号数之间的转换
C 语言中,强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。
C 语言中,几乎所有的机器都使用补码。通常,大多数数字都默认为有符号的。但是,由于 C 语言同时包含有符号和无符号数表达式的处理方式,出现了一些奇怪的行为,如果他的一个运算数是有符号的而另一个是无符号的,那么 C 语言会隐式的地将有符号参数强制类型转换位无符号数,并假设这两个数都是非负的,来执行这个运算。
扩展位表示、截取数字
不同字长的整数之间转换,同时又保持数值不变。当目标数据类型太小以至于不能表示想要的值时,这根本是不可能的。然而,从一个较小的数据类型转换到一个较大的类型,应该总是可能的。两个原理如下:
截取数字暂未看懂,待补充......
总结
C 语言都支持有符号数和无符号数,但是他们之间的隐式的转化会带来很多意想不到的错误或者漏洞,避免这一类错误的方式就是绝不使用无符号数。除了 C 以外很少有语言支持无符号整数。
在某些时刻无符号带来的麻烦会比益处多得多,比如 Java 只支持有符号整数,并且要求以补码运算来实现。但是当我们想要把字仅仅看作是位的集合而没有任何数字意义的时候,无符号数值是非常有用的,例如地址。
无符号数使用的取舍还是主要看使用者以及用途,如何使用也是一门学问。
不过怎么说呢。。。感觉公式那里模模糊糊的,然后做起练习有些题目也是没有思路。并且觉得记不住,概念方面也好,公式也好,重点也好,感觉都记不住。可能看图记住一些,但是总是感觉看着有些累,但至少比教材好了很多吧。后面继续。。。
念念不忘,必有回响。
PS:如果觉得文章不错或者帮到了您,帮忙点点下面广告呗~谢谢啦~