20135323符运锦-----信息安全系统设计基础第三周学习总结
学习计时:共10小时
读书:5h
代码:0.5h
作业:2.5h
博客:2h
一、学习过程
(一)
无符号(unsigned)编码基于传统的二进制表示法,表示 大于或者等于零的数字。补码(two's-complement)编码是表示有符号整数的最常见的方式,有 符号整数就是可以为正或者为负的数字。浮点数(floating-point)编码是表示实数的科学记数法 的以二为基数的版本。计算机用这些不同的表示方法实现算术运算,例如加法和乘法,类似于对 应的整数和实数运算。 当结果太大不能表示时会溢出,大量安全漏洞都是由于算数运算引发的.
(二)进制转换
在二进制表示法中,它的值域是000000002 ~ 111111112 ;如果用十进制整数表示,它的值域就是010 ~ 25510。十六进制(简写为"hex")使用数字'0'~'9',以及字符'A'~'F'来表示16 个可能的值。在C 语言中,以0x 或0X 开头的数字常量被认为是十六进制的值。字符'A'~'F'既可以是大写,也可以是小写,甚至是大小写混合。例如,我们可以将数字FA1D37B16 写作0xFA1D37B,或者0xfald37b,也可以写0xFa1D37b。在本书中,我们将使用C 表示法来表示十六进制值。
十六进制数字0 1 2 3 4 5 6 7
十进制值0 1 2 3 4 5 6 7
二进制值0000 0001 0010 0011 0100 0101 0110 0111
十六进制数字8 9 A B C D E F
十进制值8 9 10 11 12 13 14 15
二进制值1000 1001 1010 1011 1100 1101 1110 1111
二进制和十六进制之间的转换比较简单直接,因为可以一次执行一个十六进制数字的转换。把十六进制值B、D 和E 转换成十进制值时,则可以通过计算它们与前三个值的相对关系来完成十进制和十六进制表示之间的转换需要使用乘法或者除法来处理一般情况。将一个十进制数字x 转换为十六进制,可以反复地用16 除x,得到一个商q 和一个余数r,也就是x = q×16 + r。
然后,我们用十六进制数字表示的r 作为最低位数字,并且通过对q 反复进行这个过程得到剩下的数字反过来,将一个十六进制数字转换为十进制数字,我们可以用相应的16 的幂乘以每个十六进制数字。
(三)
每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为w 位的机器而言,虚拟地址的范围为0 ~ 2w-1,程序最多访问2w 个字节。
程序员应该力图使他们的程序在不同的机器和编译器上是可移植的。可移植性的一个方面就是使程序对不同数据类型的确切大小不敏感。C 语言标准对不同数据类型的数字范围设置了下界(这点在后面还将讲到),但是却没有上界。
C 声明 32 位机器 64 位机器
char 1 1
short int 2 2
int 4 4
long int 4 8
long long int 8 8
char * 4 8
float 4 4
double 8 8
(四)
—最低有效字节在最前面的方式,称为小端法(little endian)。大多数Intel 兼容机都采用这种规则。后一种规则—最高有效字节在最前面的方式,称为大端法(big endian)。大多数IBM 和Sun Microsystems 的机器都采用这种规则。注意我们说的是"大多数"。这些规则并没有严格按照企业界限来划分。比如,IBM 和Sun 制造的个人计算机使用的是Intel 兼容的处理器,因此用的就是小端法。许多比较新的微处理器使用双端法(bi-endian),也就是说可以把它们配置成作为大端或者小端的机器运行。
小端是"高对高、低对低",大端与之相反
(五)
(六)
布尔运算& 对应于逻辑运算AND,在命题逻辑中用符号∧表示。当P 和Q 都为真时,我们说P ∧ Q 为真。相应地,只有当p =1 且q =1 时,p & q 才等于1。布尔运算|对应于逻辑运算OR,在命题逻辑中用符号∨表示。当P 或者Q 为真时,我们说P ∨ Q 成立。相应地,当p =1 或者q =1 时,p | q 等于1。布尔运算^ 对应于逻辑运算异或,在命题逻辑中用符号表示。当P 或者Q 为真但不同时为真时,我们说P Q 成立。相应地,当p =1 且q =0,或者p =0 且q =1 时,p^q 等于1。
位向量一个很有用的应用就是表示有限集合。我们可以用位向量[aw-1,...,a1,a0] 编码任何子集A {0,1,…,w -1},其中ai = 1 当且仅当i ∈ A。例如,(记住我们是把aw-1 写在左边,而将a0 写在右边),位向量a =3 [01101001] 表示集合A = {0, 3, 5, 6},而b =3 [01010101] 表示集合B = {0,2,4,6}。使用这种编码集合的方法,布尔运算 | 和& 分别对应于集合的并和交,而~ 对应于于集合的补。
语言还提供了一组移位运算,以便向左或者向右移动位模式。对于一个位表示为[xn-1,xn-2,…,x0] 的操作数x,C 表达式x<<k 会生成一个值,其位表示为[xn-k-1,xn-k-2,…,x0,0,…,0]。也就是说,x 向左移动k 位,丢弃最高的k 位,并在右端补k 个0。移位量应该是一个0 ~ n-1之间的值。移位运算是从左至右可结合的,所以x<<j<<k 等价于(x<<j)<<k。有一个相应的右移运算x>>k,但是它的行为有点微妙。一般而言,机器支持两种形式的移:逻辑右移和算术右移。逻辑右移在左端补k 个0,得到的结果是[0,…,0,xn-1,xn-2,…,xk]。算术右移是在左端补k 个最高有效位的值,得到的结果是[xn-1,…,xn-1,xn-1,xn-2,…,xk]。这种做法看上去可能有点奇特,但是我们会发现它对有符号整数数据的运算非常有用。C语言的特性:支持按位布尔运算,确定一个位级表达式的结果的最好方法就是将十六进制转化为二进制进行运算后再转回十六进制。
(七)
这里给出来的唯一一个与机器相关的取值范围是大小指示符long 的。大多数64 位机器使用8 个字节表示,比32 位机器上使用的4 个字节表示的取值范围大很多。
数据类型long long 是在ISO C99 中引入的,它需要至少8 个字节表示。
我们可以看出B2Tw 是一个从长度为w 的位模式到TMinw 和TMaxw 之间数字的映射,写做B2Tw:{0,1}w → {-2w-1,…,2w-1-1}。同无符号表示一样,在可表示的取值范围内的每个数字都有一个唯一的w 位的补码编码。
第一,从图2-8 和图2-9 可以看到,补码的范围是不对称的:|TMin| = |TMax| + 1,也就是说,TMin 没有与之对应的正数。正如我们将会看到的,这导致补码运算的某些特殊的属性,并且容易造成程序中细微的错误。之所以会有这样的不对称性,是因为一半的位模式(符号位设置为1 的数)表示负数,而一半的数(符号位设置为0 的数)表示非负数。因为0 是非负数,也就意味着能表示的正数比负数少一个。第二,最大的无符号数值刚好比补码的最大值的两倍大一点:UMaxw = 2 TMaxw + 1。补码表示中所有表示负数的位模式在无符号表示中都变成了正数。图2-13 也给出了常数-1 和0 的表示。注意-1 和UMax 有同样的位表示—一个全1 的串。
(七)
C 语言允许无符号数和有符号数之间的转换。转换的原则是底层的位表示保持不变。因此,在一台采用补码的机器上,当从无符号数转换为有符号数时,效果就是应用函数U2Tw,而从有符号数转换为无符号数时,就是应用函数T2Uw,其中w 表示数据类型的位数。
当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C 语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。就像我们将要看到的,这种方法对于标准的算术运算来说并无多大差异
(八)
将一个无符号数转换为一个更大的数据类型,我们只需要简单地在表示的开头添加0,这种运算称为零扩展(zero extension)。将一个补码数字转换为一个更大的数据类型可以执行符号扩展(sign extension),规则是在表示中添加最高有效位的值的副本。由此可知,如果我们原始值的位表示为[xw-1,xw-2,…,x0],那么扩展后的表示就为[xw-1,…,xw-1,xw-1,xw-2,…,x0]。(我们用浅灰色标出符号位xw-1 来突出它们在符号扩展中的角色。)
一个算术运算溢出,是指完整的整数结果不能放到数据类型的字长限制中去
关于整数运算的最后思考正如我们看到的,计算机执行的"整数"运算实际上是一种模运算形式。表示数字的有限字长限制了可能的值的取值范围,结果运算可能溢出。我们还看到,补码表示提供了一种既能表示负数也能表示正数的灵活方法,同时使用了与执行无符号算术相同的位级实现,这些运算包括加法、减法、乘法,甚至除法,无论运算数是以无符号形式还是以补码形式表示的,都有完全一样或者非常类似的位级行为。们看到了C 语言中的某些规定可能会产生令人意想不到的结果,而这些可能是难以察觉理解的缺陷的源头。我们特别看到了unsigned 数据类型,虽然它概念上很简单,但可能导致即使是资深程序员都意想不到的行为。我们还看到这种数据类型会以出乎意料的方式出现,比如,当书写整数常数和当调用库函数
(九)
当一个数字不能被准确地表示为这种格式时,就必须向上调整或者向下调整,此时就会出现舍入。
前一节中谈到的定点表示法不能很有效地表示非常大的数字。例如,表达式5 × 2100 是用
101 后面跟随100 个零组成的位模式来表示。相反地,我们希望通过给定x 和y 的值,来表示形
如x × 2y 的数。
IEEE 浮点标准用V = (-1)s × M × 2E 的形式来表示一个数:
• 符号(sign) s 决定这个数是负数(s=1)还是正数(s=0),而对于数值0 的符号位解释作
为特殊情况处理。
• 尾数(significand) M 是一个二进制小数,它的范围是1 ~ 2-ε,或者是0 ~ 1-ε。
• 阶码(exponent) E 的作用是对浮点数加权,这个权重是2 的E 次幂(可能是负数)。
将浮点数的位表示划分为三个字段,分别对这些值进行编码:
• 一个单独的符号位s 直接编码符号s。
• k 位的阶码字段exp = ek-1…e1e0 编码阶码E。
• n 位小数字段frac = fn-1…f1 f0 编码尾数M,但是编码出来的值也依赖于阶码字段的值是否
等于0。
有k 位阶码和n 位小数的浮点表示的一般属性。
• 值+0.0 总有一个全为0 的位表示。
• 最小的正非规格化值的位表示,是由最低有效位为1 而其他所有位为0 构成的。它具有小
数(和尾数)值M = f = 2-n 和阶码值E = -2k-1 + 2。因此它的数字值是V = 2-n-2k-1+2。
• 最大的非规格化值的位模式是由全为0 的阶码字段和全为1 的小数字段组成的。它有小
数(和尾数)值M = f = 1-2-n(我们写成1-ε)和阶码值E = -2k-1 + 2。因此,数值V =
(1-2-n)× 2-n-2k-1+2,这仅比最小的规格化值小一点。
• 最小的正规格化值的位模式的阶码字段的最低有效位为1,其他位全为0。它的尾数值M =
1,而阶码值E = -2k-1 + 2。因此,数值V = 2-2k-2
值1.0 的位表示的阶码字段除了最高有效位等于1 以外,其他位都等于0。它的尾数值是
M = 1,而它的阶码值是E= 0。
• 最大的规格化值的位表示的符号位为0,阶码的最低有效位等于0,其他位等于1。它的小
数值f = 1-2-n,尾数M = 2-2-n(我们写作2- ε)。它的阶码值E = 2k-1-1,得到数值V =
(2-2-n)×22k-1-1 = (1-2-n-1)×22k-1
相关的区域对应于整数的低位,刚好在等于1 的最高有效位之前停止(这个位就是隐含的开头的位1),和浮点表示的小数部分的高位是相匹配的。
当在int、float 和double 格式之间进行强制类型转换时,程序改变数值和位模式的原则如下(假设int 是32 位的):
• 从int 转换成float,数字不会溢出,但是可能被舍入。
• 从int 或float 转换成double, 因为double 有更大的范围( 也就是可表示值的范
围),也有更高的精度(也就是有效位数),所以能够保留精确的数值。
• 从double 转换成float,因为范围要小一些,所以值可能溢出成为+ ∞或- ∞。另外,
由于精确度较小,它还可能被舍入。
• 从float 或者double 转换成int, 值将会向零舍入。例如,1.999 将被转换成1,
而-1.999 将被转换成-1。进一步来说,值可能会溢出。C 语言标准没有对这种情况指
定固定的结果。与Intel 兼容的微处理器指定位模式[10...00](字长为w 时的TMinw)为
整数不确定(integer indefinite)值。一个从浮点数到整数的转换,如果不能为该浮点数
找到一个合理的整数近似值,就会产生这样一个值。因此,表达式(int)+1e10 会得
到-21483648,即从一个正值变成了一个负值。
作业重点题目:
2.4、2.6、2.8、2.11、2.13、2.14、2.18、2.19、2.21、2.23、
2.24、2.25、2.27、2.29、2.33、2.34、2.39、2.40、2.42、2.43、
2.44、2.45、2.47、2.50、2.52、2.54
(八)家庭作业(与20135323余佳源共同完成)
选座内容:
答案:
小组作业中,我承担的是解读题目以及解题思路的任务,余佳源承担的是代码编写。
解读题目:当无符号数x包含偶数个1时,返回值为1,否则为返回值为0,假设x的数据类型是int 有w=32位。
解题思路:要求x所包含的1的个数,可以对x的每个位进行异或运算。如果得到的结果是0,那么就说明x包含偶数个1,则返回值为1,;如果得到的结果是1,那么说明x包含奇数个1,则返回值为0。
代码如下:
int even_ones(unsigned x){
x ^= (x >> 16);//等同于x=x^(x>>16)
x ^= (x >> 8); //等同于x=x^(x>>8)
x ^= (x >> 4); //等同于x=x^(x>>4)
x ^= (x >> 2); //等同于x=x^(x>>2)
x ^= (x >> 1); //等同于x=x^(x>>1)
return !(x&1);
}
二、遇到的问题及解决方案
1.在2.2.4中出现错误,反复查询原因,发现位模式不变,尽管数值会做出一下改变。
2.在2.2.7中不知数值为何改变,最后发现截断值会使数值溢出,故改变。