信息安全系统设计基础第三周学习总结
【教材第二章:信息的表示和处理】
【学习时间:12小时】
一、教材知识点
1.无符号数、有符号数(2进制补码)、浮点数,从逆向角度考虑为什么会产生漏洞?
【任何漏洞产生都必然因为系统不可更改的局限性——>无符号数、有符号数、浮点数的局限性——>无符号数或者有符号数的表示范围有限,而浮点数虽然编码范围大,但是不精确】
2.在C语言中,所有以0X或者0x开头的数字常量都常被认为是十六进制的值
3.计算机的字长指明的最重要的系统参数是虚拟地址空间的最大大小(字长为w,则程序最多访问2^w个字节)
4.gcc -m32 可以在64位机上生成32位的代码
5.字节顺序的两种表示方法:小端是“高对高、低对低”,大端与之相反
6.代码执行
#include <stdio.h>
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, int len)
{
int i;
for(i = 0;i<len;i++)
{
printf("%.2x",start[i]);
}
printf("
");
}
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(ival);
show_float(fval);
show_pointer(pval);
}
void main()
{
int val;
printf("please enter an int:
");
scanf("%d",&val);
test_show_bytes(val);
}
以12345为例,输出结果如下:
(图1)
说明,该笔记本(yoga2)为小端法机器。同时,还了解到,不同的机器或者操作系统使用不同的存储分配原则。
7.文本数据比二进制数据具有更好的平台适应性。
8.只要一个与非门,就可以完成所有的逻辑运算。
9.要用C99中的“long long”类型,编译是要用 gcc -std=c99
10.B2T中,最高位权重为-2^(w-1)。这也可以解释,利用补码可以把数学运算统一成加法
11.将有符号数强转成无符号数之后,数字的数值发生改变,然而其位表示不变。(如,将-12345的补码看作无符号数字的话,则为53191.经过类型强转得到的结果也是53191)
12.T2Uw(x)=x+2^w,x<0;U2Tw(x) = x-2^w,x>=2^(w-1)
13.几乎所有的机器都使用补码;通常,大多数数字都默认为有符号.然而,在一个运算式中,如果有一个参数是无符号的 ,那么另外一个也会默认为无符号数
14.怎么样让负数等于正数?由练习题2.21可以得到一些启示:在负数x后加上U,可以使其转换为(2^w+x)
15.零扩展类似于逻辑左(右)移。即:将一个无符号数转换为一个更大的数据类型,简单地在前面加上0。符号扩展类似于算数左(右)移。即:将一个补码数字转换为一个更大的数据类型,在表示中添加最高有效位值得副本。注意:在进行数据大小、有无符号的转换时;如果要进行从short到unsigned的转换,必须经过short->int->unsigned
16.数字截断:将一个w位的数丢弃前k位得到的数字(二进制)为:2^w mod 2^k,有可能发生溢出.
17.算数运算的溢出,是指完整的整数结果不能放到数据类型的字长限制中去。比如,两个数的和为2^w或者更大时,就发生了溢出。
18.补码的非:除了x=-2^(w-1)和0的非是他们本身之外,其他数字的非(加法逆元)都可以通过2^w-x获得。
19.C语言中,有符号数字的乘法是通过将2w位截断为w位的方式来实现的。即:xy - U2T((xy)mod 2 ^w)
20.表达式x*K,其中,K可以表示为一组从位位置n到m连续的1和其他位置的0,那么,表达式可以表示为:
A:(x<<n)+(x<<n-1)+……+(x<<m) B:(x<<n+1)-(x<<m)
21.除以2的幂可以用移位运算实现(右移)。事实证明,移位总是摄入到0,这一结果与整数除法的规则一样。对于非负数,算术右移和逻辑右移都可以作为除法运算;而负数则是进行算术右移。
22.浮点数对形如V = X*2^Y的数字进行编码,主要是很接近于0或者很大的数字。当一个数字不能被精确地表示为这种形式时,就必须要向上或者向下调整,即为舍入。
23.IEEE浮点标准——用V= (-1)^sM2^E来编码一个数。其中:
符号:s决定这个数是负数(s=1)还是正数(s=0),对于数值是0的符号位解释为特殊情况。 尾数:M是一个二进制小数 阶码:E对浮点数加权,可以是负数
根据以上,float:s=1位,exp=8位,frac=23位
double:s=1位,exp=11位,frac=52位
24.整数与浮点数表示同一个数字的关系?
整数与浮点数表示同一个数字时,化成二进制形式之后,可以看到,整数等于1 的最高有效位之后的数字,与浮点数小数部分的高位是相匹配的。
25.整数与浮点数转换规则?
整数->浮点数:整数转换成二进制表示,然后小数点左移若干位得到规格化表示;取出小数部分的数值,在后面补0使其达到23位; 用frac加上偏置量得到的结果用二进制表示,放在取出的部分前面,再加上一个符号位即可。
26.利用这一特性:x/y = (x+y-1)/y。在移位(除法)之前“偏置”这个值,通过这种方法修正不合理的输入。
27.在IEEE浮点数表示中,以规格化表示的阶码字是以偏置形式表示的有符号数。
28.当阶码全为1、小数域全为0时,得到的值表示无穷;当阶码全为1、小数域不全为0时,结果是NaN(not a number)
二、课本练习题筛选
1.
0x503c+0x8 = 0x5044
0x503c-0x40 = 0x4ffc
0x503c+64 = 0x5070(原始答案错误。原因:未看到64前面没有十六进制标识)
2.写出0x00359141、0x4a564504的二进制表示。并移动相对位置使其尽量匹配
0x00359141 = 0000 0000 0011 0101 1001 0001 0100 0001(2进制)
0x4a564504 = 0100 1010 0101 0110 0100 0101 0000 0100(2进制)
0000 0000 001101011001000101000001
*************************
0100 1010 010101100100010100000100
共有21位相匹配。整数基本上所有有效位都嵌在浮点数中。
3.a = [01101001],b = [01010101]。计算:
~a = [10010110]
~b = [10101010]
a&b = [01000001]
a |b = [01111101]
a^b = [00111100]
4.写一段代码实现一个数组的头尾元素依次交换
代码如下:
#include<stdio.h>
#define MAX 10
void inplace_swap(int *x,int *y)
{
*y = *x^*y;
*x = *x^*y;
*y = *x^*y;
}
void reverse_array(int a[], int cnt)
{
int first,last;
for(first = 0,last = cnt-1;first<=last;first++,last--)
inplace_swap(&a[first], &a[last]);
}
void main()
{
int a[MAX];
int count,i;
printf("please enter the amount of numbers( no more than %d):
",MAX);
scanf("%d",&count);
printf("please enter numbers('e' as the end)
");
for(i = 1;i<=count;i++)
{
scanf("%d
",&a[i-1]);
}
printf("the original array is as follow:
");
for(i = 1;i<=count;i++)
{
printf("%d ",a[i-1]);
}
reverse_array(a, count);
printf("the new array is as follow:
");
for(i = 1;i<=count;i++)
{
printf("%d ",a[i-1]);
}
}
这段代码分别以1,2,3,4和1,2,3,4,5作为输入的时候,结果如下:
(图2)
(图3)
即:当数组长度为奇数的时候,输出的结果最中间的数字变为0.原因?在最后一次调用inplace_swap的时候,传入的first和last都是原数组中最中间的数字;在第一处*y = x^y时,y指向的数字就变为了0.此后,0变作为最中间的数字参与循环。解决办法?将循环条件中的first<=last 改为first<last(最中间的数字不会参与循环)即可。
5.假设有两个函数实现位设置bis和位清除bic操作;只利用这两个函数实现按位 | 和^操作。
int bis(int x, int y);
int bis(int x, int y);
int bool_or(int x,int y)
{
int result = bis(x,y);
return result;
}
int bool_xor(int x,int y)
{
int result = bis(bic(x,y),bic(y,x));
return result;
}
本题初始解答错位。原因?未掌握 x^y = (x&~y) | (~x&y)
6.假设x,y的字节值分别是0x66,0x39,求:
x & y = 0x20
x | y = 0x7f
~x | ~y = 0xdf
x&!y = 0x00
x && y = 0x01
x || y = 0x01
!x || !y = 0x00
7.对于32位补码形式显示的十六进制值,转化为十进制
0x1b8 = 110
0xfffffe58 = -424
8.
T2TU4(-8) = 8 T2U4(-3) = 13 T2U(-2) = 14 T2U4(0) = 0
9.假设在运用补码运算32位机器上对以下这些表达式求值
-2147483647-1 == 2147483648U 无符号 1 -2147483647-1<2147483647 有符号 1 -2147483647-1U <2147483647 无符号 0 -2147483647-1U <-2147483647 无符号 1
10.假设在一个采用补码运算的32位机器上执行这些函数。计算下列输入参数之后的结果:
#include<stdio.h>
int fun1(unsigned word)
{
return (int)((word<<24)>>24);
}
int fun2(unsigned word)
{
return ((int)word<<24)>>24;
}
void main()
{
int word;
printf("please enter a number:
");
scanf("%d",&word);
printf("the result of fun1:%d
",fun1(word));
printf("the result of fun2:%d
",fun2(word));
}
分析:fun1()是将word进行过逻辑左移和右移的结果转换为int型;而fun2()是将word先强制转换为int型,随后进行的算数左移和右移
w:0x00000076 fun1(w)=0x00000076,fun2(w)=0x00000076
w:0x87654321 fun1(w)=0x00000021,fun2(W)=0x00000021 (此处起初和答案有冲突。最初认为在fun2()中,w转换为的int型应该表示的是一个负数,所以逻辑移动时应该补1.后来意识到,是先进行左移即右侧六个十六进制位补f,随后右移的时候,因为最高有效位是0,所以前侧六个十六进制位补0)
w:0x000000c9 fun1(w)=0x000000c9 fun2(w)=0xffffffc9
11.假设将一个4位数值(0——f)截断为一个3位数值(0——7),填写截断后的结果。
原始值:0 无符号截断值:0 补码截断值:0
原始值:2 无符号截断值:2 补码截断值:2
原始值:9 无符号截断值:1
原始值:b 无符号截断值:3
原始值:15 无符号截断值:7
原始值:-7 补码截断值:1
原始值:-1 补码截断值:-1
12.以下代码试图计算数组a[]中所有元素的和,然而当参数length=0时,会发生存储器错误。试解释原因并修改代码。
#include<stdio.h>
#define MAX 100
float sum_elements(float a[], unsigned length)
{
int i;
float result = 0;
for (i =0;i<=length-1;i++)
{
result+=a[i];
}
return result;
}
void main()
{
float a[MAX];
unsigned number;
int i;
printf("Please enter the amount of numbers in your array:
");
scanf("%u",&number);
if(number <0)
{
printf("Wrong!
");
return;
}
if(number == 0)
{
printf("the result is:%f
",sum_elements(a, number));
return;
}
else
{
printf("Please enter the elements:(the tail of array should be end by 'e')
");
for(i = 0;i<=number-1;i++)
{
scanf("%f
",&a[i]);
}
printf("the result is:%f
",sum_elements(a, number));
return;
}
}
以正常的浮点数数组{1.2,2.6,5.7,8.6}作为输入,得到正常结果:
(图5)
如果以length=0输入,结果如下
(图6)
解释:原因应该在“i<=length-1”与之前声明的“unsigned length”的矛盾中。因为当输入的length是0时,length-1=0-1(无符号数运算),即模数加法,得到的是Umax。而任何数都是小于Umax的,所以比较式恒为真。则循环会访问数组a中的非法元素。简单的处理办法就是将length声明为int型。
13.任务:写一个函数判定一个字符串是否比另一个更长。函数如下:
size_t strlen(const char *s);
int strlonger(char *s,char *t)
{
return strlen(s)-strlen(t) >0;
}
在什么情况下会产生不正确的结果?
当s的长度小于t的长度时,strlen(s)-strlen(t) 仍然以无符号数进行运算,则会产生模数加法,使其恒大于零。
如何修改代码?
把return strlen(s)-strlen(t) >0改成return strlen(s)>strlen(t)即可。
14.写一个具有如下原型的函数:
int uadd_ok(unsigned x,unsigned y)
当x和y的和不发生溢出时,返回1
答: int uadd_ok(unsigned x,unsigned y)
{
unsigned sum = x+y;
return sum >=x;
}`
因为机器运算能力的有限性,不可能使用x+y>2^w这样的判断语句。所以通过判断两数之和是否大于某一个被加数这样的间接方式确定。
15.计算下列16进制数字的加法逆元。
F 十进制:15 逆元:1 0 十进制:0 逆元:0
16.求:
无符号:x=[100],y=[101] xy = 010100=20,截断后的xy= 100=4
有符号:x=[100],y=[101] xy = 001100=12,截断后的xy= 100=-4 (初始解答:x=[100],y=[101] xy = 10100=-12,截断后的xy= 100=-4。错误。因为:有符号的补码运算,如果运算数有负数,应该把正确结果转换成二进制)
17.对于位位置n为有效位的情况,我们要怎么样修改表达式B:(x<<n+1)-(x<<m)?
表达式变为了-(x<<m)。解析(不同于课本):n为有效位,则K为补码表示的负数,将其(认为K的二进制表示中只有n到m位是1,其余位是0)转换为二进制的绝对值形式后,发现其二进制表示中只有第m位是1,其余位都是0。那么,x*k就变成了-(x<<m)
18.写一个函数div16,对任何整数参数,返回x/16的值。不能使用四则运算和任何条件运算符、比较运算符。(假设你的机器是32位,使用补码表示,右移都是算术右移)
(分析:正整数的除法运算很容易就通过>>4移位实现,然而还需要考虑负数的情况。如果不能使用条件语句,就要写出满足正负数的通用公式。)
int div16(int x)
{
int bias = (x>>31)&0xf;//如果是负数,bias就会变成f
return (x+bias)>>4;
}
19.在下面的代码中,我们定义了常数M和N
#define M
#define N
int arith(int x,int y)
{
int result = 0;
result = x*M+y/N;
return result;
}
以下是将机器代码翻译为C语言的结果:
int optarith(int x,int y)
{
int t = x;
x<<5;
x-=t;
if(y<0) y+=7;
y>>3;
return x+y
}
问:M和N是多少? 1.x左移五位即乘32,减去1之后相当于乘31;则M=31
2.y右移三位相当于除以8;则N=8
20.假设我们对有符号数字使用补码运算。变量的初始化和声明如下:
int x = foo();
int y = bar();
unsigned ux = x;
unsigned uy = y;
对下面每个表达式,证明其真假。
1)x*x >=0 假。可以x = 65535为例;此时,按照有符号数乘法,得到的数转换为二进制之后为0xFFFE001,为负数。
2)x~y+uxuy ==-x 真。~y等于-y-1,uxuy == xy。因此,等式左右两边等价。
三、问题总结
1.P63:
通过类似的推理,我们可以得出,对于一个位模式为[x(w-1),x(w-2),……,0,……,0]的补码数x,以及在0<=k<=w范围内的任一k,位模式为[x(w-k-1),x(w-k-2),……,0,……,0]就是x*2^k的补码表示
为什么截断前面的k位、后面补上0之后,就是一个乘式结果的补码表示?
P66练习题2.42
写一个函数div16,对任何整数参数,返回x/16的值。不能使用四则运算和任何条件运算符、比较运算符。(假设你的机器是32位,使用补码表示,右移都是算术右移)
int div16(int x)
{
int bias = (x>>31)&0xf;//如果是负数,bias就会变成f
return (x+bias)>>4;
}
不太理解如何证明负数运算时,加上bias(即f)之后就可以直接右移四位?
P67练习题2.44
E.x>0||-x>0
假。设x=-2147483648(Tmin32),则x和-x都为负数
如何判断x的相反数是多少?
四、学习心得
通过本次“边读书,边思考,边记录”的过程,我深刻地意识到“精读”背后要多付出的精力、时间,更体会到与泛读甚至浏览完全不在同一个层次上的收获。第二章一共60页,坚持每一页的每一句话(不能保证是每一个字)都看到心里去,说不乏味不疲倦是不可能的。在十一假期里,我用了四天的时间,每天都看上几十页,积少成多地去读书。然而,时间上的持久带来了短期记忆模糊的问题。这时候,靠自己的笔记就可以有效地弥补记忆上的模糊,让知识点和代码“夯实”在脑子里。
这样的收获是显著的;我开始跟随老师所提出的要点去走,跟着书中的思路去走,书中所提到的引用与老师所提到的要点就好像路标,我看到了所有的路标之后,路就变得格外好走。