C++项目总结二之内存溢出

来源:互联网 发布:百度人口迁徙数据 编辑:程序博客网 时间:2024/06/05 22:50

内存溢出用一个比较形象的比喻就好比向一个容量300ml的杯子中倒水。如果倒的水超出300ml,水就溢出。在程序中如果我们写入(或读取)数据的范围超出了变量的“容量”就可有可导致未知的程序行为。本方将从两个方面讨论内存溢出问题,一种是写入的数据超出变量“容量”;另一种是访问的数据超出变量范围,这种情况称为越界访问;大多数时候访问溢出内存区域不会造成程序异常行为。
一、字符处理函数使用不当
1.字符串strcpy
程序1:

#include <stdio.h>#include <string.h>int main(){    char    szBuf[5] = {0};    strcpy(szBuf,"hello");    return 0;}

在上面的程序中,数组szBuf在栈上申请了5字节大小内存。strcpy拷贝了5个字符到szBuf中,由于c语言中字符串以’\0’结束。所以实际是向szBuf中写入了”hello”加’\0’共6个字节,而数组szBuf只占用了5字节;在vs2008 Debug模式下运行程序后提示我们变量szBuf数据损坏,如下图所示。如果我们的程序在运行过程出现了这种弹框提示,就说明我们的程序出现的溢出现象。这里写图片描述

图1

2、sprintf函数
在程序开发过程中,有时会将数据格式化输出到字符串缓冲区中。在我们的项目中有时对字符连接也会使用格式化输出函数。
程序2:

#include <stdio.h>#include <string.h>int main(){    int     iNum = 123;    char    szBuf[5] = {0};    sprintf(szBuf,"%d456",iNum);    return 0;}

在上面的程序中我们将整型iNum格式化后与字符串”456”一起生成字符串”123456”。然后将字符串”123456”存放到szBuf缓冲区中。而szBuf只有5个字节大小,在vs2008中运行上面程序,会报如图1类似的提示内存溢出提示。
3、memcpy函数使用不当
在我们做的一个项目中需要获取网卡的连接名称。网卡连接名称是通过系统API函数调用,获取的网卡连接名称放到一个系统的结构体变量中,然后再将网卡名使用memcpy拷贝到局部变量中,程序大致如下:
程序3:

#include <stdio.h>#include <string.h>int main(){    char    szBuf[64] = {0};    //为了简化假设已获取的网卡名称已存入到szNetcarName中    char    szNetcarName[128] ;    memcpy(szBuf,szNetcarName,strlen(szNetcarName));    return 0;}

上面的程序隐患很隐蔽,并且程序大部分情况下都能正常运行。直接有一天在一台装了虚拟机的电脑上运行该系统时。程序突然崩溃,后来通过定位发现原来装的虚拟网卡有一网卡名称有100个字符左右。我们的szBuf才64字符,我们给memcpy的第三个参数传入的参数是strlen(szNetcarName),这时就会向szBuf写入100个字符左右,造成内存溢出,程序崩溃。
对于不安全字符串处理函数造成的内存溢出可以使用下面的解决办法:1.使用编译器,或标准库中具有相同功能的安全函数,如strcpy替换成strncpy。2.使用c++的stl string或者开源的第三方字符串处理类,如tinyxml的TiXmlString。3.自己编写字符处理的类
二、循环变量使用不当
1.循环变量数据类型
程序4:

#include <stdio.h>#include <stdio.h>#include <string.h>void func(char *szBuf){    for(unsigned int i=strlen(szBuf)-1;i>=0;i--)    {        if(szBuf[i]>='A' && szBuf[i]<='Z')        {            szBuf[i] -= 32;        }    }}int main(){    func("hello");    return 0;}

上面的程序两处问题:1.如果传给func的字符串是个空串,由于在for循环中定义的i是unsigned int型;unsigned int取值范围为[0,0xFFFFFFFF],所以在for循环中i>=0这个判断条件会一直成立。2.如果传给func的函数的参数是空串,i=strlen(szBuf)-1的值为-1(在内存的表示为0xFFFFFFFF),由i是unsigned int型,所以在循环体中执行szBuf[i]时会出现访问数组下标为0xFFFFFFFF情况,造成程序异常退出。
2.循环判断条件
程序5:

#include <stdio.h>#include <string.h>void EncodeBase64(char *szBuf,char *szSource){    int i=0;    int j=0;    for(i=0;i<1021;i+=4)    {        for(j=0;j<strlen(szSource)/3;j+=3)        {            //将字节编码编码成字节,编码后的字节分别放在szBuf[i],szBuf[i+1],szBuf[i+2],szBuf[i+3]中。        }    }    //处理szSource乘余的字节}int main(){    char szBuf[1022] = {0};    EncodeBase64(szBuf,"ABCDEF");    return 0;}

上面程序是对字符串进行base64编码。在EncodeBase64函数的外层循环中,判断szBuf可使用的下标范围。分析下程序5中的外层循环,假如待加密字符串足够长,i每次增加4,执行255次循环后,i的值是1020;此时i<1021,程序继续执行,经过base64加密后的4字节数据分别存放在1020,1021,1022,1023处,这样就会造成内存溢出。如果1022,1023处存放的是函数返回地址,程序就会出现诡异的情况(后面我们会做一个简单的实验,通过内存溢出实现函数诡异调用)。

原创粉丝点击