☰
C++使用过程中的溢出问题详解
2026/10/1 11:14:33 网站建设 项目流程

内容:在C/C++程序里有一类非常典型的问题,那就是:溢出问题。现在分别来分析一下常见的数组溢出,整数溢出,缓冲区溢出,栈溢出和指针溢出等。

1、数组溢出

在C语言中,数组的元素下标是从0开始计算的,所以,对于n个元素的数组a[n], 遍历它的时候是a[0],a[1],...,a[n-1],如果遍历到a[n],数组就溢出了。

1

2

3

4

5

6

7

8

voidprint_array(inta[],intn)

{

for(inti = 0; i < n; i++)

{

a[i] = a[i+1];//当i = n-1时,就发生了数组越界

printf(“%d\n”, a[i]);

}

}

上面的循环判断应该改为:
for (int i = 0; i < n-1; i++)

2、整数溢出

整数的溢出分为下溢出和上溢出。比如,对于有符号的char(signed char)类型来说,它能表示的范围为:[-128,127]之间;而对于无符号的char(unsigned char)来说, 它能表示的范围为:[0,255]。

那么,对于下面的代码:

1

2

3

4

5

6

7

8

signedcharc1 = 127;

c1 = c1+1;//发生上溢出,c1的值将变为-128

signedcharc2 = -128;

c2 = c2-1;//发生下溢出,c2的值将变为127

unsignedcharc3 = 255;

c3 = c3+1;//发生上溢出,c3的值将变为0

unsignedcharc4 = 0;

c4 = c4-1;//发生下溢出,c4的值将变为255

从上面的例子可以看出,当一个整数向上溢出,将会变为最小值,而向下溢出,将会变为最大值。

来看下面的溢出代码,该代码负责提供一个小写字母转换表,但存在一个整数溢出问题:

1

2

3

4

5

6

7

8

9

10

11

voidBuildToLowerTable(void)/* ASCII版本*/

{

unsignedcharch;

/* 首先将每个字符置为它自己 */

/*ch为unsigned char,无符号数,当ch值为UCHAR_MAX, ch++将会发生向上溢出,变为0,导致循环无法退出。*/

for(ch=0; ch <= UCHAR_MAX;ch++)

chToLower[ch] = ch;

/* 将大写字母改为小写字母 */

for( ch = ‘A'; ch <= ‘Z'; ch++ )

chToLower[ch] = ch +'a'– ‘A';

}

该代码负责在内存中查找指定的字符ch,但也存在一个溢出问题

1

2

3

4

5

6

7

8

9

10

11

12

void*memchr(void*pv, unsignedcharch,size_tsize )

{

unsignedchar*pch = (unsignedchar*) pv;

/*当size的值为0的时候,由于size是无符号整数,因此会发生下溢出,变为一个最大的整数 循环也将无法退出*/

while( -- size >=0 )

{

if( *pch == ch )

return(pch );

pch++;

}

return( NULL );

}

3、缓冲区溢出

缓冲区溢出一般是调用了一些不安全的字符串操作函数比如:strcpy,strcat等(这些字符串操作函数在拷贝或者修改目标位置的时候,并不判断长度是否会超过目标缓存),或者设置参数超过了目标缓存能容纳的大小而造成的溢出问题。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

voidfunc1(char* s)

{

charbuf[10];

/*此时,buf只有10个字节,如果传入的s超过10个字节,就会造成溢出*/

strcpy(buf, s);

}

voidfunc2(void)

{

printf("Hacked by me.\n");

exit(0);

}

intmain(intargc,char* argv[])

{

charbadCode[] ="aaaabbbb2222cccc4444ffff";

DWORD* pEIP = (DWORD*)&badCode[16];

*pEIP = (DWORD)func2;

/*badCode字符串超过了10个字节,传递给func1会造成栈上缓冲区溢出

而且,由于badCode经过精心构造,在溢出的时候,根据函数的调用约定规则,会覆盖栈上的返回地址,

指向了func2。所以,在func1退出的时候,会直接调用func2

*/

func1(badCode);

return0;

}

4、栈溢出

无论是内核栈,还是应用层的栈,都是有一定大小限制的。如果在栈上分配的空间大于了这个限制,就会造成栈大小溢出,破坏栈上的数据。比如局部变量过多,或者递归调度嵌套太深都会造成栈溢出。比如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

intinit_module(void)

{

charbuf[10000];//buf[]分配在栈上,但10000的空间超过了栈的默认大小8KB。

//所以发生溢出

memset(buf,0,10000);

printk("kernel stack.\n");

return0;

}

voidcleanup_module(void)

{

printk("goodbye.\n");

}

MODULE_LICENSE("GPL");

//应用栈的大小对少?内核栈的大小多少?什么时候容易栈溢出?

5、指针溢出

一块长度为size大小的内存buffer,buffer的首地址为p,那么buffer最后一个字节的地址:
p+size-1,而不是p+size。如果写成了p+size,就会造成溢出,比如下面的代码:

1

2

3

4

5

6

7

8

9

10

11

12

void*memchr(void*pv, unsignedcharch,size_tsize )

{

unsignedchar*pch = ( unsignedchar* )pv;

unsignedchar*pchEnd = pch + size;

while( pch < pchEnd )

{

if( *pch == ch )

return( pch );

pch ++ ;

}

return( NULL );

}

上面的代码用于查找内存中特定的字符位置。对于其中的while()循环,平时执行似乎都没有任何问题。但是,考虑一种特别情况,即pv所指的内存位置为末尾若干字节,那么因为pchEnd = pch+size,所以pchEnd指向最后一个字符的下一个字节,将会超出内存的范围,即pchEnd所指的位置已经不存在。
知道了问题所在,那么可以将内存的结尾计算方式改为:

1

2

3

4

5

6

7

pchEnd = pv + size – 1;

while( pch <= pchEnd )

{

if( *pch == ch )

return( pch );

pch ++ ;

}

pchEnd指向了最后一个字节。但是,检查循环内部的执行情况可知,由于pch每增加到pchEnd+1时,都会发生上溢。因此,循环将无法退出。 于是,可以将程序修改为下面的代码。将用size变量来控制循环的退出。这样就不会存在任何问题了。

1

2

3

4

5

6

7

8

9

10

11

void*memchr(void*pv, unsignedcharch,size_tsize )

{

unsignedchar*pch = ( unsignedchar* )pv;

while( size -- > 0 )

{

if( *pch == ch )

return( pch );

pch ++;

}

return( NULL );

}

大家知道,--size的效率一般比size--的效率高。那么是否可以将循环的判断条件改为下面的语句呢?
while( --size >= 0 )
……

实际上这是不行的。因为当size=0时,由于size是无符号数,那么它将发生下溢,变成了size所能表示的最大正数,循环也将无法退出。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询