第二章 信息的表示和处理
2.1 信息存储
三种最重要的数字编码:
- 无符号:基于传统的二进制表示法,表示大于等于0的数字
- 二进制补码是便是有符号整数的最常见方式。
- 浮点数是表示实数的科学计数法的以二为基数的版本。
由于精度有限,浮点数运算是不可结合的。计算顺序会影响到结果,后面浮点一节会详细介绍
大多数计算机使用8位的块,也叫做字节,用来作为最小的可寻址的存储器单位。每个字节都有一个地址,所有的可能地址就构成了虚拟地址空间
。机器级程序将整个存储器视为一个非常大的字节数组。
2.1.1 十六进制表示法
在脑海中记住A,C,F对应的十进制和二进制数值,其他的BDE就可以通过跟前面的相对关系来得到。
A 10 1010, C 12 1100 F 15 1111
A和F比较好记,A是10,二进制是两个10. F全是1 。
快速计算一个2的幂的十六进制方法:
当x是2的幂时,即有 n , x = 2 n n,x=2^n n,x=2n, 我们就可以很容易的将x写成16进制形式。x的二进制表示就是1后面跟n个0。而十六进制是四个二进制变一个数,从右往左算四个一组,最后不足4位的可以补0。所以十六进制形式中后面有n/4个0,而首位则是1跟n%4个0组成的二进制。例如, x = 2048 = 2 11 x = 2048 = 2^{11} x=2048=211, 11/4 = 2, 11%4 = 3, 于是首位为 1000 即0x8, 后跟3个十六进制的0,即 0x800 。
空有屠龙记,现在有啥情况是要自己算十六进制的?
2.1.3. 字长
每台计算机都有一个字长(上一章提到的总线宽度)。整数和指针都使用该大小。字长也决定了系统虚拟地址空间的大小。
在移植的时候,一些隐藏对字长依赖就会显现出来。比如,许多程序员假设int可以存储一个指针,这在大多数32位机器上都工作正常,但是在一台64位机器上可能会导致问题。
2.1.4. 寻址和字节顺序
多余多个字节的数据,对象的地址一般都是字节序列中的最小地址
。
地址都是从小到大,如果多字节数据的低位存储在低地址称为小端,反之是大端
。以下几种情况,字节顺序会有影响:
- 网络传输,需要做字节顺序转换
- 阅读表示数据的字节序列时,例如汇编产生的代码
- 当编写规避正常的类型系统的程序时。例如使用char指针指向int类型时,取到的值是高位或者低位完全不一样。该方法也能确认当前机器是大端还是小端。
大小端的说法来自《格列佛游记》,说有两个王国持续大战了三年,而战争的原因是吃鸡蛋从大的一端打开还是小的一端打开引起的争执。就像豆腐脑的甜咸之争。
2.1.5 表示字符串 2.1.6 表示代码
字符串比二进制数据具有更强的平台独立性。
十进制数字0-9字符的16位数值正好是 0x30 - 0x39,10进制表示就是48到57。
计算机系统的一个基本概念就是从机器角度来看,程序仅仅只是字节序列。机器没有关于原始源程序的任何信息。
2.1.7 布尔代数和环
1850年乔治·布尔设计出的一种代数。
DeMorgan定律
~(a&b) = ~a | ~b;
~(a|b) = ~a & ~b;
// 吸收性
a | (a&b) = a;
a & (a|b) = a;
// 相补性
a | ~a = 1;
a & ~a = 0;
// 幂等性
a & a = a;
a | a = a;
什么是环?一种代数描述,其中+操作可以跟负数操作互逆,比如 a+(-a) = 0; 将^视为+,将I单位操作视为负 ,则A^ I(A) = A^A = 0 ,则<{0,1},^,&,I,0,1>也是个环
基本上就是看不懂,到底啥是环。好在这个概念不影响后续阅读。
位向量可以用来表示有限集合。例如[01101001] 表示 {0,3,5,6} 第n位在集合中存在就为1,否则为0,向量表示从右到左表示,从第0位开始。集合数值较大则不适用。实际应用中使用位来设置参数,如 config = 1 | 2 | 4 | 8等
C代码如何不用第三个数来交换两个值?使用异或,A^A = 0 A^0 = A
; 利用这两条:
x = x ^ y;
y = x ^ y;
x = x ^ y
即可理解为: x^y^y = x^0 = x; x^y^x = y^0 = y;
注意:此方法并没有什么性能上的优势,仅是一直智力上的消遣
。
这里A^A=0是所谓的环的属性。
异或加密的理论支持就在于 x ^ y ^ y = x 使用相同的关键秘钥,即可还原文本。最简单的方法就是逐字节异或,则秘钥的取值范围为1-255范围较小,容易被破解。
那么可以考虑多字节异或,则破解难度大大提高。需要考虑文件尾部部分字节不足的处理。
位运算的一个常见用法就是掩码。例如0xFF就表示获取数值的低8位值。 表达式~0将生成一个全1的掩码,不管机器的字的大小。
-1也能得到全1的掩码吗?
0异或是对方,1异或是对方的补。
2.1.9 C中的逻辑运算
逻辑运算的一个重要特性是短路属性,即前面部分如果已经能确定值,就不会对第二个参数进行运算。
UE4的蓝图中的bool and 没有这个特性,需要注意
题目:使用位和逻辑运算来表示 x == y 的判断。
由x^x = 0 可得 ,如果 x^y = 0 则 x == y。 所以表达式为 !(x^y)
2.1.10 C中的位移运算
左移没什么好说的, 低位补0 。而右移存在两种不同的行为方式:即高位补0的逻辑右移
和填充最高位的算术右移
。对于无符号数,明确必须使用逻辑右移,即高位填充0,而对于有符号数值
,则两种都可以,而实际上基本上所有的实现都是采用填充高位值
。(实测确实如此)
就是说 1 >> 1 结果是0 而 -1 >> 1 -1 >> 10 都还是-1 ,-1不管怎么右移都还是-1
。
2.2 整数表示
整数可以分为两种:有符号和无符号。而具体的数值由其位数来决定。例如C中有char,short,int,long等。
冷知识:Java只支持有符号数,不支持无符号数。
2.2.2 无符号和二进制补码编码
假设一个无符号整数二进制位有w位,则它的二进制可以表示为
B 2 U w ( x ⃗ ) = ∑ i = 0 w − 1 x i 2 i B2U_w(\vec x) = \sum_{i=0}^{w-1}x_i2^i B2Uw(x)=i=0∑w−1xi2i
它能表示的最小值是[0…0]即0,能表示的最大值是[1…1], 也即 2 w − 1 2^w - 1 2w−1 .
例如32位unsigned int 取值范围则为 [ 0 , 2 32 − 1 ] [0 , 2^{32} - 1] [0,232−1]
对于负数,则使用补码,补码可以用函数来表示为:
B 2 T w ( x ⃗ ) = − x w −