【教材第二章:信息的表示和处理】
【学习时间:12小时】
【任何漏洞产生都必然因为系统不可更改的局限性——>无符号数、有符号数、浮点数的局限性——>无符号数或者有符号数的表示范围有限,而浮点数虽然编码范围大,但是不精确】
#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("\n");
}
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:\n");
scanf("%d",&val);
test_show_bytes(val);
}
以12345为例,输出结果如下:
(图1)
说明,该笔记本(yoga2)为小端法机器。同时,还了解到,不同的机器或者操作系统使用不同的存储分配原则。
A:(x<<n)+(x<<n-1)+……+(x<<m) B:(x<<n+1)-(x<<m)
符号:s决定这个数是负数(s=1)还是正数(s=0),对于数值是0的符号位解释为特殊情况。 尾数:M是一个二进制小数 阶码:E对浮点数加权,可以是负数
根据以上,float:s=1位,exp=8位,frac=23位
double:s=1位,exp=11位,frac=52位
整数与浮点数表示同一个数字时,化成二进制形式之后,可以看到,整数等于1 的最高有效位之后的数字,与浮点数小数部分的高位是相匹配的。
整数->浮点数:整数转换成二进制表示,然后小数点左移若干位得到规格化表示;取出小数部分的数值,在后面补0使其达到23位; 用frac加上偏置量得到的结果用二进制表示,放在取出的部分前面,再加上一个符号位即可。
0x503c+0x8 = 0x5044
0x503c-0x40 = 0x4ffc
0x503c+64 = 0x5070(原始答案错误。原因:未看到64前面没有十六进制标识)
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位相匹配。整数基本上所有有效位都嵌在浮点数中。
~a = [10010110]
~b = [10101010]
a&b = [01000001]
a |b = [01111101]
a^b = [00111100]
代码如下:
#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):\n",MAX);
scanf("%d",&count);
printf("please enter numbers('e' as the end)\n");
for(i = 1;i<=count;i++)
{
scanf("%d\n",&a[i-1]);
}
printf("the original array is as follow:\n");
for(i = 1;i<=count;i++)
{
printf("%d ",a[i-1]);
}
reverse_array(a, count);
printf("the new array is as follow:\n");
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(最中间的数字不会参与循环)即可。
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)
x & y = 0x20
x | y = 0x7f
~x | ~y = 0xdf
x&!y = 0x00
x && y = 0x01
x || y = 0x01
!x || !y = 0x00
0x1b8 = 110
0xfffffe58 = -424
T2TU4(-8) = 8 T2U4(-3) = 13 T2U(-2) = 14 T2U4(0) = 0
-2147483647-1 == 2147483648U 无符号 1 -2147483647-1<2147483647 有符号 1 -2147483647-1U <2147483647 无符号 0 -2147483647-1U <-2147483647 无符号 1
#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:\n");
scanf("%d",&word);
printf("the result of fun1:%d\n",fun1(word));
printf("the result of fun2:%d\n",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
原始值:0 无符号截断值:0 补码截断值:0
原始值:2 无符号截断值:2 补码截断值:2
原始值:9 无符号截断值:1
原始值:b 无符号截断值:3
原始值:15 无符号截断值:7
原始值:-7 补码截断值:1
原始值:-1 补码截断值:-1
#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:\n");
scanf("%u",&number);
if(number <0)
{
printf("Wrong!\n");
return;
}
if(number == 0)
{
printf("the result is:%f\n",sum_elements(a, number));
return;
}
else
{
printf("Please enter the elements:(the tail of array should be end by 'e')\n");
for(i = 0;i<=number-1;i++)
{
scanf("%f\n",&a[i]);
}
printf("the result is:%f\n",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型。
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)即可。
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这样的判断语句。所以通过判断两数之和是否大于某一个被加数这样的间接方式确定。
F 十进制:15 逆元:1 0 十进制:0 逆元:0
无符号: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。错误。因为:有符号的补码运算,如果运算数有负数,应该把正确结果转换成二进制)
表达式变为了-(x<<m)。解析(不同于课本):n为有效位,则K为补码表示的负数,将其(认为K的二进制表示中只有n到m位是1,其余位是0)转换为二进制的绝对值形式后,发现其二进制表示中只有第m位是1,其余位都是0。那么,x*k就变成了-(x<<m)
(分析:正整数的除法运算很容易就通过>>4移位实现,然而还需要考虑负数的情况。如果不能使用条件语句,就要写出满足正负数的通用公式。)
int div16(int x)
{
int bias = (x>>31)&0xf;//如果是负数,bias就会变成f
return (x+bias)>>4;
}
#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
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。因此,等式左右两边等价。
通过类似的推理,我们可以得出,对于一个位模式为[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之后,就是一个乘式结果的补码表示?
写一个函数div16,对任何整数参数,返回x/16的值。不能使用四则运算和任何条件运算符、比较运算符。(假设你的机器是32位,使用补码表示,右移都是算术右移)
int div16(int x)
{
int bias = (x>>31)&0xf;//如果是负数,bias就会变成f
return (x+bias)>>4;
}
不太理解如何证明负数运算时,加上bias(即f)之后就可以直接右移四位?
E.x>0||-x>0
假。设x=-2147483648(Tmin32),则x和-x都为负数
如何判断x的相反数是多少?
通过本次“边读书,边思考,边记录”的过程,我深刻地意识到“精读”背后要多付出的精力、时间,更体会到与泛读甚至浏览完全不在同一个层次上的收获。第二章一共60页,坚持每一页的每一句话(不能保证是每一个字)都看到心里去,说不乏味不疲倦是不可能的。在十一假期里,我用了四天的时间,每天都看上几十页,积少成多地去读书。然而,时间上的持久带来了短期记忆模糊的问题。这时候,靠自己的笔记就可以有效地弥补记忆上的模糊,让知识点和代码“夯实”在脑子里。
这样的收获是显著的;我开始跟随老师所提出的要点去走,跟着书中的思路去走,书中所提到的引用与老师所提到的要点就好像路标,我看到了所有的路标之后,路就变得格外好走。