信息表示和处理
-
现代计算机存储和处理信息以二进制表示。
-
三种重要的数字表示:
- 无符号编码:基于传统的二进制表示法,表示>=0的数字
- 补码编码: 表示有符号整数的最常见的方式
- 浮点数编码: 是表示实数的科学计数法的以2位基数的版本
-
计算机的表示法是用有限数量的位来对一个数字编码,因此结果溢出表示范围时,会出现异常的结果。
-
整数和浮点数的表示区别
- 整数只能编码一个相对较小的数值范围,但是这种表示是精确的。
- 浮点数虽然可以表示一个较大的数值范围,但是这种表示是近似的
2.1 信息存储
- 大多数计算机使用字节(byte),作为最小的可寻址的内存单位,而不是访问单独的位(bit)
- 机器级程序将内存视为一个非常大的字节数组,称为
虚拟内存 (virtual memory)
- 内存中的每个字节都由一个数字进行标识,即为它的
地址(address)
- 所有可能的地址集合,称为
虚拟地址空间(virtual address space)
- 每个程序对象都可以视为一个字节块,程序本身就是一个字节序列
- C语言中一个指针(
无论它指向一个整数或是一个结构,或是一个对象等等
)的值就是某个存储块的第一个字节的虚拟地址。
2.1.1 十六进制表示法
- 一个字节由8位组成,值的范围用二进制表示 00000000 ~ 11111111,用十进制表示位0~255。使用过程中,如果用二进制表示太冗长,如果用十进制表示则与二进制转换太麻烦。所以引入了16进制。
- 十六进制:使用数字’0’ ~'9’以及字符 ‘A’ ~ ‘F’(大小写都可)表示16个可能的值。
- c语言中以0x或0X开头来标识十六进制,所以一个字节的值范围即为 0x00-0xFF。一个十六进制位由4个二进制位表示
2.1.2 字数据大小
-
字长(word size)指明指针数据的标称(nominal size)大小。
-
字长
决定
的最重要的系统参数就是虚拟地址空间的最大大小
对于一个字长为w位的机器而言,虚拟地址的范围为0~(2的w次方-1)。即程序最多访问(2的w次方)个字节
-
32位字长的机器限制的虚拟地址空间为4GB(
2的32次方个byte
),64位的字长的机器限制的虚拟地址空间为16EB(1.84 * 10的19次方个字节
) -
32位程序和64位程序的区别
1 区别在于该程序是如何编译的,而不是其运行的机器类型 prog.c使用下列伪指令编译 linux>gcc -m32 prog.c 32位程序 linux>gcc -m64 prog.c 64位程序 2 程序是向后兼容的,所以32位的程序可以运行到64位的机器上。但是64位的程序不能运行在32位的机器上。
-
c语言对各种不同数据类型,在32位和64机器上分配的字节数
有符号 无符号 32位 64位 [signed] char unsigned char 1 1 short unsigned short 2 2 int unsigned 4 4 long unsigned long 4 8 int32_t uint32_t 4 4 int64_t uinit64_t 8 8 char * 4 8 float 4 4 double 8 8 我们可以看到有些数据类型的确切字节数依赖于程序是如何被编译的,数据类型long在32位程序中为4字节,在64位程序中为8字节。为了避免依赖"典型"大小和不同编译器设置带来的奇怪行为。在ISO C99引入了一类数据类型,其数据大小是固定的,不随编译器和机器设置而改变。其中就有int32_t,int64_t。
2.1.3 寻址和字节顺序
-
对于跨越多字节的程序对象,我们必须建立两个规则:
- 这个对象的地址是什么
- 在内存如何排列这些字节
-
多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。
例如: int类型的变量x的地址为0x100,即为地址表达式&x的值为0x100。 那么变量x的4个字节将被存储在0x100, 0x101, 0x102, 0x103位置
-
排列换一个对象的字节有两个通用的规则
- 小端法(little endian): 最低有效字节在最前面
- 大端法(big endian):最高有效字节在最前面
假设变量x的类型为int, 位于地址0x100处,它的十六进制值为0x01234567, 其最高字节为0x01,最低字节为0x67,其地址范围为0x100-0x103。 大端法 ... 0x100 0x101 0x102 0x103 ... 01 23 45 67 小端法 ... 0x100 0x101 0x102 0x103 ... 67 45 32 01 1 大端法和小端法没有优劣之分。一个系统采用的是大端还是小端,要看厂商。一旦选择了操作系统,那么字节顺序也就固定下来了。 2 现代有些新的微处理器是双端法,也就是可以配置作为大端或小端的机器运行 3 Android和iOS采用的是小端法。
2.1.4 表示字符串
2.1.4.1 ASCII码表
- 终端执行
$ man ascii
八进制表示
000 nul 001 soh 002 stx 003 etx 004 eot 005 enq 006 ack 007 bel
010 bs 011 ht 012 nl 013 vt 014 np 015 cr 016 so 017 si
020 dle 021 dc1 022 dc2 023 dc3 024 dc4 025 nak 026 syn 027 etb
030 can 031 em 032 sub 033 esc 034 fs 035 gs 036 rs 037 us
040 sp 041 ! 042 " 043 # 044 $ 045 % 046 & 047 '
050 ( 051 ) 052 * 053 + 054 , 055 - 056 . 057 /
060 0 061 1 062 2 063 3 064 4 065 5 066 6 067 7
070 8 071 9 072 : 073 ; 074 < 075 = 076 > 077 ?
100 @ 101 A 102 B 103 C 104 D 105 E 106 F 107 G
110 H 111 I 112 J 113 K 114 L 115 M 116 N 117 O
120 P 121 Q 122 R 123 S 124 T 125 U 126 V 127 W
130 X 131 Y 132 Z 133 [ 134 \ 135 ] 136 ^ 137 _
140 ` 141 a 142 b 143 c 144 d 145 e 146 f 147 g
150 h 151 i 152 j 153 k 154 l 155 m 156 n 157 o
160 p 161 q 162 r 163 s 164 t 165 u 166 v 167 w
170 x 171 y 172 z 173 { 174 | 175 } 176 ~ 177 del
十六进制表示
00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
十进制表示
0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel
8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si
16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb
24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us
32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 '
40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 /
48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7
56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ?
64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G
72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O
80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W
88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _
96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g
104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o
112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w
120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del
2.1.4.2 字符串
-
c语言中字符串被编码为一个以null(其值为0)字符结尾的字符数组。每个字符都以某个标准编码来表示。最常见的就是ASCII字符码。
对字符串 "12345", 参照ASCII码表,其十六进制字节码为 31 32 33 34 35 00。
-
在使用ASCII字符码的任何系统上都将得到相同的结果,与字节顺序和字大小规则无关。因此,文本数据比二进制数据具有更强的平台独立性。
文字编码的Unicode标准
1 ASCII字符集适合编码英语文档,无法表示特殊字符,如中文,俄文等语言。
2 Unicode统一字符集,使用32位来表示字符。这好像是要求每个字符要占用4个字节。
3 不过,常见的字符只需要1个或2个字节,而不太常用的字符需要多一些字节数。
4 UTF-8将每个字符编码为一个字符序列,其包含ASCII编码。
5 Java使用Unicode来表示字符串。对于C语言也有支持Unicode的程序库。
2.1.5 表示代码
下面的c函数
int sum(int x, int y) {
return x + y;
}
当在不同机器上编译时,生成如下字节表示的机器代码;
Linux32 55 89 e5 8b 45 0c 03 45 08 c9 c3
Windows 55 89 e5 8b 45 0c 03 45 08 5d c3
Sun 81 c3 e0 08 90 02 00 09
Linux64 55 48 89 e5 89 7d fc 89 75 f8 03 45 fc c9 c3
-
我们发现指令编码是不同的,不同机器类型使用不同的且不兼容的指令和编码方式。
-
即使完全一样的进程,运行在不同的操作系统上也会有不同的编码规则,因此二进制代码是不兼容的。
-
二进制代码很少能够在不同机器和操作系统组合之间进行移植。
2.1.6 布尔代数简介
简而言之,就是使用 &(与)
,|(或)
,~(非)
,^(异或)
进行位运算
位向量 [0110] 和 [1100]进行位运算
0110 0110 0110
& 1100 | 1100 ^ 1100 ~ 1100
---- ---- ---- ----
0100 1110 1010 0011
1 运算规则
& 同真为真
| 一真为真
& 不同为真
~ 按位取反
2 对于任何值与自己本身异或,值为0
x ^ x = 0
3 利用^来交换x和y的值
x = x ^ y
y = x ^ y ===> (y = x ^ y ^ y) ====> (y = x )
x = x ^ y ===> (x = x ^ y ^ x) ====> (x = y )
2.1.7 C语言中的位级运算
C的表达式 | 二进制表达式 | 二进制结果 | 十六进制表达式 |
---|---|---|---|
~0x41 | ~[0100 0001] | [1011 1110] | 0xBE |
~0x00 | ~[0000 0000] | [1111 1111] | 0xFF |
0x69&0x55 | [0110 1001]&[0101 0101] | [0100 0001] | 0x41 |
0x69|0x55 | [0110 1001]|[0101 0101] | [0111 1101] | 0x7D |
-
正如上表所示,进行位运算时,将其他进制转换成二进制,然后进行位运算后,再转回所需的进制。
-
位级运算的一个常见用法是实现掩码运算。这里的掩码是一个位模式,表示从一个字中
选出的
位的集合。1. 对于一个字长为32位的机器,掩码0xFF表示一个字的最低字节。 2. 对于x = 0x89ABCDEF, x&0xFF = 0x000000EF, 只会保留x的最低字节,其他字节均为0。 3. ~0将会生成一个全为1的掩码,不管机器的字大小是多少。 4. 尽管对于32位的机器来说,掩码可以写为0xFFFFFFFF,但是这样的代码不是可移植的。
2.1.8 C语言中的逻辑运算
-
逻辑运算符:
||
,&&
,!
。 -
逻辑运算中认为所有非0的参数都表示true,参数0表示false。
表达式 结果 !0x41 0x00 !0x00 0x01 !!0x41 0x01 0x69&&0x55 0x01 0x69||0x55 0x01 -
具有
短路机制
,如果第一个表达式的结果已经能够确认整个表达式的值,那么第二个表达式就不会进行运算。
2.1.9 C语言中的移位运算
-
左移:对于操作数x, 向左移动k位,将丢弃最高的k位,并在右端补k个0
操作数x: 0101 1001 (01)0110 0100 向左移动2位,最高的2位被丢弃,右端补2个0 //无符号数和有符号数都用左移
-
右移: 逻辑右移和算术右移
逻辑右移:向右移动k位,低位丢弃,高位补0 算术右移:向右移动k位,低位丢弃,高位补最高有效位的值 算术右移:用于操作有符号数 逻辑右移:用于操作无符号数
-
移位示例
操作 值 参数x (0110 0011) (1001 0101) x << 4 (0011 0000) (0101 0000) x >> 4 逻辑右移 (0000 0110) (0000 1001) x >> 4 算术右移 (0000 0110) (1111 1001) -
C语言虽然没有规定对于有符号数应该运用哪种形式的右移。但是,实际上,几乎所有的编译器/机器组合都对有符号数使用算术右移,且程序员也是假设机器对有符号数使用算术右移。
-
对于无符号数,右移必须是逻辑的。
-
Java对右移有明确的定义。
x >> k
, 会将x做算术右移x>>>k
,会对x做逻辑右移
-
一个数w位,当移动的位数k大于等于w时,该如何处理?
实际移动的位数是 = k % w; int lval = 0xFEDCBA98 << 32 w=32 k = 32 向左位移0位 =====》0xFEDCBA98 int aval = 0xFEDCBA98 >> 36 w=32 k = 36 向右位移4位 =====》0xFFEDCBA9 int uval = 0xFEDCBA98u >> 40 w=32 k = 40 向右位移8位 =====》0xFFFEDCBAu
2.2 整数表示
- 用位来编码整数的两种不同的方式: 一种只能表示非负数。另一种能够表示正数,零,负数。
2.2.1 整型数据类型
-
32位程序上, C语言整型数据类型的典型取值范围
[外链图片转存失败(img-lLTpf5gz-1564652354825)(./image/2-32位范围.png)]
-
64位程序上, C语言整型数据类型的典型取值范围
[外链图片转存失败(img-fpZCJMLd-1564652354828)(./image/2-64位范围.png)]
-
C语言标准定义了美中数据类型必须能够表示的最小的取值范围
[外链图片转存失败(img-pBtWYbE3-1564652354828)(./image/2-最低范围.png)]
2.2.2 无符号的编码
对于一个w位的无符号编码,其值的范围为0 ~ (2的w次方 - 1)
2.2.3 补码编码
-
补码是最常见的表示有符号数的形式。其最高位为符号位。它的权重为
-2[w-1](即为-2的w-1次方)
由下列补码求其值 0001 = - 0 * 2[3] + 0 * 2[2] + 0 * 2[1] + 1 * 2[0] = 0 + 0 + 0 + 1 = 1 0101 = - 0 * 2[3] + 1 * 2[2] + 0 * 2[1] + 1 * 2[0] = 0 + 4 + 0 + 1 = 5 1011 = - 1 * 2[3] + 0 * 2[2] + 1 * 2[1] + 1 * 2[0] = -8 + 0 + 2 + 1 = -5 1111 = - 1 * 2[3] + 1 * 2[2] + 1 * 2[1] + 1 * 2[0] = -8 + 4 + 2 + 1 = -1
-
从上面的计算中可以得到w位的补码所能表示的值的范围
其最小值为 [10....0](w) = -2[w-1] 其最大值为 [01....1](w) = 2[w-1