C语言中的整型溢出

定义


整型溢出指的是整型变量在进行赋值或者运算时,得到的结果超过了其取值范围从而发生截断,导致结果和预期不符的现象
整型溢出也分为无符号和有符号的溢出或者可以分为上溢出overflow或者下溢出underflow
对于unsigned整型溢出,C的规范是有定义的,“溢出后的数会以2^(8*sizeof(type))作模运算”,也就是说,如果一个unsigned char(1字符,8bits)溢出了,会把溢出的值与256求模
举例来说:

unsigned char x = 0xff;

printf("%d\n", ++x);

上面的代码会输出:0 (因为0xff + 1是256,与2^8求模后就是0)
对于signed整型的溢出,C的规范定义是“undefined behavior”,也就是说,编译器爱怎么实现就怎么实现。对于大多数编译器来说,算得啥就是啥。比如:

unsigned char x = 0x7f;

printf("%d\n", ++x);

上面的代码会输出:-128,因为0x7f + 0x01得到0x80,也就是二进制的1000 0000,符号位为1,负数,后面为全0,就是负的最小数,即-128。
一般编译器都会按照这种规则对有符号数进行溢出处理,其他的整型类型也同理。

溢出的危害
 

1、死循环

... ...
.
... ...
short len = 0;
.
... ...
while(len< MAX_LEN) {
 
    len += readFromInput(fd, buf);
 
    buf += len;
}

若MAX_LEN是一个较大的整型,超过了32767,因为short类型的取值范围是-32768~32767 ,len的值永远不会超过32767,所以while中的循环体会一直循环下去不会停止


2、转型时溢出

int copy_something(char *buf, int len)
{
    #define MAX_LEN 256
    char mybuf[MAX_LEN];
    ... ...
    ... ...
    if(len > MAX_LEN){ // <---- [1]
        return -1;
    }
    return memcpy(mybuf, buf, len);
}


若传入的len值为负数,就能通过检查直接入参到memcoy函数中,而memcpy则需一个size_t的len,也就是一个unsigned 类型,传入函数时会进行整型提升为一个正数,这个正数会有可能大于MAX_LEN,导致mybuf中的缓冲区后面的数据被重写,越界访问。


3、分配内存

nresp = packet_get_int();
if (nresp > 0) {
    response = xmalloc(nresp*sizeof(char*));
    for (i = 0; i < nresp; i++)
        response[i] = packet_get_string(NULL);
}


代码中,nresp是size_t类型(size_t一般就是unsigned int/long int),这个示例是一个解数据包的示例,一般来说,数据包中都会有一个len,然后后面是data。
一般在32位系统中,指针占4个字节,若nresp>0xffffffff/4,则会出现整型溢出,真正分配的内存远小于预期的分配量,在for中的循环中,访问的的内存超过所分配的空间出现内存访问越界的问题。

4、缓冲区溢出

int func(char *buf1, unsigned int len1, char *buf2, unsigned int len2 )
{
 
   char mybuf[256]; 

   if((len1 + len2) > 256){    //<--- [1]
 
       return -1;
 
   } 

   memcpy(mybuf, buf1, len1);
 
   memcpy(mybuf + len1, buf2, len2); 

   do_some_stuff(mybuf); 

   return 0;
}


函数的目的是将buf1和buf2中的内容一起拷贝至mybuf中,为了防止溢出还做了检查,但是如果len1+len2的结果溢出则会躲过检查出现拷贝时越界访问内存的问题

5、size_t 的溢出

for (int i= strlen(s)-1;  i>=0; i--)  { ... }
for (int i=v.size()-1; i>=0; i--)  { ... }

strlen()和vector::size()返回的都是 size_t,size_t在32位系统下就是一个unsigned int。如果strlen(s)和v.size() 都是0则会导致结果为 (unsigned int)(-1),最大的正整数,出现溢出的情况,造成越界访问的问题

防止溢出的方法

编译器对某些有符号的整型溢出是一个未定义的行为,在编译器编译的过程中会把这部分的代码进行偏离我们目的的优化,这种奇怪的行为会导致无法检测溢出

1、正确的检测的溢出

在运算导致的溢出之前,必须要进行溢出检测

void foo(int m, int n)
{
    size_t s = m + n;
 
    if ( m>0 && n>0 && (SIZE_MAX - m < n) ){
 
        //error handling...
    }
}


以上的代码存在两个问题
1、有符号转为无符号
2、整型溢出
所以需要在计算之前进行检查

为什么不使用n+m<SIZE_MAX的原因是因为n+m的结果溢出后截断还是小于SIZE_MAX,所以恒为真难以检测出来,另外,这个表达式中,m和n分别会被提升为unsigned
但是还是错误的,因为
1、在计算后检查,此时m+n的有符号的溢出是未定义的行为
2、所以在计算SIZE_MAX - m < n会被编译器优化
3、SIZE_MAX是size_t的最大值,size_t在64位系统下是64位的,严谨点应该用INT_MAX或是UINT_MAX

正确的代码应该为

void foo(int m, int n)
{
    size_t s = 0;
 
    if ( m>0 && n>0 && ( UINT_MAX - m < n ) ){
 
        //error handling...
 
        return;
 
    }
 
    s = (size_t)m + (size_t)n;
}


2、二分查找法中的溢出
一般的二分查找法

int binary_search(int a[], int len, int key)
{
 
    int low = 0; 
 
    int high = len - 1; 

    while ( low<=high ) {
 
        int mid = (low + high)/2;
 
        if (a[mid] == key) {
 
            return mid;
 
        }
 
        if (key < a[mid]) {
 
            high = mid - 1;
 
        }else{
 
            low = mid + 1;
 
        }
 
    }
 
    return -1;
}

1、int mid = (low + high)/2会出现溢出的问题
2、无论len是否为无符号数,若len为0则会导致向下溢出

所以应该在计算mid的时候

int mid = low + (high - low)/2;

3、上溢出和下溢出的检查
加法检查
 

#include <limits.h>


void f(signed int si_a, signed int si_b) {
 
    signed int sum;
 
    if (((si_b > 0) && (si_a > (INT_MAX - si_b))) ||
 
        ((si_b < 0) && (si_a < (INT_MIN - si_b)))) {
 
        /* Handle error */
 
        return;
 
    }
 
    sum = si_a + si_b;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值