博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
二进制溢出
阅读量:6364 次
发布时间:2019-06-23

本文共 4232 字,大约阅读时间需要 14 分钟。

一、溢出的本质

溢出的本质是计算机无法存放过大或者过小的数据。

假设一个计算机CPU是4位的,那么每一位或者为0,或者为1,根据排列组合,这四位最多共有2*2*2*2=16种可能的组合方式,也就是说这台计算机只能最多表示16个数字。

以计算机中的无符号整数为例,那么4位CPU的计算机表示出来的就只有0~15这16个数字。如果你拿两个数,一个为11,另一个为5,做加法的话,计算结果会显示为0而不是16。

因为11加4已经等于15了,再加1它已经无法表示,所以又回到了0处,这种情况就属于上溢。反之,2-3的话,得到的结果为15,因为2-2已经为0,再减的话就转回到了15,这属于下溢。

总之一句话,溢出反应了计算机处理能力的上限和下限,太大的数和太小的数均无法直接呈现出来。

 

二、探讨有符号数与无符号数上溢出下溢出的问题

1,有符号数的溢出

#include
Void main(){ int i= 2147483647; printf(“%d,%d”,i.i+1);}

输出结果为:2147483647,-2147483648

这是因为加减运算过后,它们的值超出了它们对应的那种整数类型的表示范围,我们把这种现象称为溢出。

注意:看清楚数字总是在循环的变化。如从最大2147483647,再加一后就变成了最小-2147483648。即循环的顺序是:

0—  2147483647—   -2147483648—  0。

规律:

SHRT_MAX+1 == SHRT_MIN

SHRT_MIN-1 == SHRT_MAX

例如:

#include 
int main () { short int a=32767,b=32767,c; a=a+b; //实现两数不借助第三变量交换的功能 printf("a=%d,b=%d\n",a,b); b=a-b; printf("a=%d,b=%d\n",a,b); a=a-b; printf("a=%d,b=%d\n",a,b); c=sizeof(short int); printf("sizeof=%d",c); return 0;}

结果:

a=-2,b=32767  因为:32767+1=-32768     -32768+32766=-2
a=-2,b=32767       因为:-2-32766 =-32768    -32768-1=32767,反正都是在[-32768,32767]之间循环
a=32767,b=32767
sizeof=2
因此,下面的程序就是错误的,while是个无限循环:
short int n=1, sum=0;while(sum<=32767) {sum+=n; n++;}printf(“n=%d\n”,n-1);

另外google的一道笔试题中也需要意识到溢出的存在:

short cal(short x){   if(x==0)    return 0;   else      return x+cal(x-1);}

答案

x=0时,0

x>0时,x+(x-1)+(x-2)+…+1+0

x<0时,x+(x-1)+…+(-32768)+【溢出】+32767+32766……+1+0,中途栈溢出,计算此表达式最后的结果时,也会多次溢出,因此最后的结果人力很难算出。

假如是short类型的负数来说,-32768减去1之后,变成32767,就是说对于有符号整数来说:最小的负数-1=最大的整数,最大的整数+1=最小的负数。

假如栈不溢出,那么将递归32768-|x|+32767次,最后的值按照上面是可以计算出来的。但是栈的空间有限,当栈溢出的时候,错误,强制退出。

在gcc下,测试,假如上述数据类型是char,最后是能计算出值的大小,栈的大小还够用。

2,下面为无符号数的溢出

在c语言的程序开发调试中,经常碰到非法操作导致程序强行终止。这种情况的发生多是因为程序指针的指向错误,数据溢出就是其中的一种,下面我将介绍一下常见的几种溢出情况。

1)无符号整数上溢

示例代码:

bool funcB(char *s1,unsigned short int len1,char *s2,unsigned short int len2){    if (1 + len1 + len2 > 64)        return false;    char *buf = (char*)malloc(len1+len2+1);    if (buf)     {        memcpy(buf,s1,len1);        /*函数解释:void *memcpy(void *to , const void *from, unsigned int count)  :从from指向的内存区向to指向的内存区复制count个字节;如果两内存区重叠,不定义该内存区的定义*/      memcpy(buf+len1,s2,len2);    }    if (buf) free(buf);    return true;}

这段代码存在整数上溢问题,当len1等于64,len2是0xFF(255),这段代码就会发生溢出。

因为在定义为unsigned short char 类型下1+0xFF=0,这样就绕过了1 + len1 + len2 > 64的条件判断,因为刚好等于64。

直接导致后面的代码中错误地分配64字节内存,在内存拷贝时将产生溢出错误。无符号整数上溢出的意思就是:无符号整数a已达最大数,+1之后又从小开始计算:1+0xFF=0。

2)无符号整数下溢

 示例代码:

bool funcA(unsigned int cbSize){  if (cbSize < 1024)  {    char *buf = new char[cbSize-1];    memset(buf,0,cbSize-1);    /*函数解释:void memset(void *buf, int ch, unsigned int count):  把ch的低字节复制到buf指向的内存区的前count个字节处,常用于把某个内存区域初始化已知值。*/    delete buf;    return true;  }  else    return false;}

这是一个整数下溢的例子。

当函数调用时,如果参数cbSize赋值为0,由于参数cbSize被定义为unsigned int 型,则(cbSize-1)= (0-1) = 0XFFFFFFFF。

分配如此大的内存,后果可想而知(4G内存,1个char是一个字节)!

无符号整数下溢就是:无符号整数a为最小值0,再-1后变成最大值,例如:(0-1) = 0XFFFFFFFF。

 

三、例子

1,

#include 
short int fac( short int x){ static short int y=1; //用static修饰,即使出了fac函数,内存也不会被回收,可以一直累计。                  静态区的变量不会被回收,直至进程结束才能回收,有别于堆或栈中的变量。 y*=x; return y;}int main(void){ int s=0; short i; for(i=1;i<=8;i++) s+=fac(i); printf("S=%d\n",s); return 1; } 运行结果:S=-19303

运行SETP:

Setp1:i=1 y=1 S=0+1=1

Setp2:i=2 y=2 S=1+2=3

Setp3:i=3 y=6 S=3+6=9

Setp4:i=4 y=24 S=9+24=33

Setp5:i=5 y=120 S=33+120=153

Setp6:i=6 y=720 S=153+720=873

Setp7:i=7 y=5040 S=873+5040=5913

Setp8:i=8 y=40320溢出-->y=-25216 S=5913+(-25216)=-19303

16位内存空间存储情况:1001,1101,1000,0000(即-25216的二进制补码表示,若表示无符号数原码则为40320)

故:y=-25216(与40320同余,模65536,因为40320-(-25216)=65536,或-25216-40320=-65536,类似于时钟,16点-4点=12点(模),又类似:-2与10互补,2与-10互补,以数值12为模)

即:将负数的补码,当成无符号数的原码,即是此负数的同余数,或叫补数。此是补码的真谛,补码即是反码,并不是相反数,而是同余,正是同余,才可以将减变成加。

比如25217-25216 = 25217+40320 = 65537,对于16位的数值来说,表示范围[-32768,0,32767],模为65536,因此上溢出,65537 = 1,因此25217-25216 = 1。

有人说看表示范围,不是到32767就溢出了吗,怎么这里到65537才溢出?没关系,表示范围是表示范围,而模是溢出的实现方法,不相关。

因此在补码计算中溢出是正确的也是必须的,这样才能得出正确的结果,这是补码的真谛。

因此,不同机器字长的计算机,内部必然设定有一个与机器字长相关的模。比如8位计算机模256,16位计算机模65536,以此实现运算。

2,

#include 
void main(){  char c1 = 128;//溢出  unsigned char c2 = 257;//溢出  short s1 = 65535;//溢出  unsigned int s2 = 65537;//不溢出  printf("%d,%d,%d,%d",c1,c2,s1,s2);}

输出结果:-128,1,-1,65537

转载于:https://www.cnblogs.com/ronglei/p/8080742.html

你可能感兴趣的文章
SecureCrt 连接Redhat linux
查看>>
[NHibernate]持久化类(Persistent Classes)
查看>>
安装oracle数据库提示“程序异常终止”解决方案
查看>>
如何在Hive中使用Json格式数据
查看>>
iOS开发网络篇—简单介绍ASI框架的使用
查看>>
linux如何恢复被删除的热文件
查看>>
Eclipse(MyEclipse) 自动补全
查看>>
Struts2中dispatcher与redirect的区别
查看>>
zabbix agentd configure
查看>>
[From OpenBSD Man Page]CARP
查看>>
地图点聚合优化方案
查看>>
Google Chrome 快捷方式
查看>>
备考PMP心得体会
查看>>
vue proxy匹配规则
查看>>
线上应用故障排查之一:高CPU占用
查看>>
Extend Volume 操作 - 每天5分钟玩转 OpenStack(56)
查看>>
review what i studied `date` - 2017-4-1
查看>>
IronPython教程
查看>>
squid via检测转发循环
查看>>
计算分页
查看>>