c语言 内存泄露内存错

提问回答都赚钱
> 问题详情
以下叙述错误的是()。A.C语言区分大小写B.C程序中的一个变量,代表内存中一个相应的存储单元,变量
悬赏:0&&答案豆&&&&提问人:匿名网友&&&&提问收益:0.00答案豆&&&&&&
以下叙述错误的是( )。A.C语言区分大小写B.C程序中的一个变量,代表内存中一个相应的存储单元,变量的值可以根据需要随时修改C.整数和实数都能用C语言准确无误地表示出来D.在C程序中,正整数可以用十进制、八进制和十六进制的形式来表示
发布时间:&&截止时间:
查看最佳答案前请先输入下方的验证!
网友回答&(共0条)
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&12.00元收益
回答悬赏问题预计能赚取&12.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&20.00元收益
回答悬赏问题预计能赚取&20.00元收益
回答悬赏问题预计能赚取&5.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&1.00元收益
回答悬赏问题预计能赚取&3.00元收益
回答悬赏问题预计能赚取&2.00元收益
为你请到的专家
&&&&采纳率:76%&&&
&&采纳率:97%&&&
&&采纳率:88%&&&
&&&&采纳率:25%&&&
&&采纳率:90%&&&
[] [] [] [] [] [] [] [] [] [] [] []C语言内存管理
C语言内存管理
对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题,为了应对这个问题,有许多技术被研究出来来解决这个问题,例如Smart
Pointer,Garbage
Collection等。一般我们常说的内存泄漏是指堆内存的泄漏。那么为什么会导致内存泄漏呢?通过学习内存管理,相信你一定能解决好这个问题。
1-1&&&&&&&&&&
C语言内存管理方式
在进入本专题前,我们先看一下下面的程序,来简单分析以下C语言的内存管理:
#include &stdio.h&
#include &malloc.h&
//全局变量定义
int iGlobalInt1=0;
int iGlobalInt2=0;
int iGlobalInt3=0;
//全局常量定义
const int iGlobalConstInt1=1;
const int iGlobalConstInt2=5;
const int iGlobalConstInt3=6;
//全局静态变量定义
static int iGlobalStaticInt1=0;
static int iGlobalStaticInt2=0;
static int iGlobalStaticInt3=0;
//函数定义
void& funcParamTest(int iFuncParam1,int
iFuncParam2,int iFuncParam3)
//函数私有变量定义
iLocalInt1=iFuncParam1;
iLocalInt2=iFuncParam2;
iLocalInt3=iFuncParam3;
&& printf("函数参数变量内存地址\n");
printf("iFuncParam1=0xx\n",&iFuncParam1);
printf("iFuncParam2=0xx\n",&iFuncParam2);
printf("iFuncParam3=0xx\n\n",&iFuncParam3);
printf("函数本地变量的内存地址\n");
printf("iLocalInt1=0xx\n",&iLocalInt1);
printf("iLocalInt2=0xx\n",&iLocalInt2);
printf("iLocalInt3=0xx\n\n",&iLocalInt3);
//入口函数
int main(int argc, char* argv[])
//局部静态变量
&& static int
iStaticInt1=0;
&& static int
iStaticInt2=0;
&& static int
iStaticInt3=0;
//局部静态常量定义
&& const static int
iConstStaticInt1=0;
&& const static int
iConstStaticInt2=0;
&& const static int
iConstStaticInt3=0;
//局部常量
&& const int iConstInt1=1;
&& const int iConstInt2=5;
&& const int iConstInt3=6;
//局部变量
iLocalInt1=0;
iLocalInt2=0;
iLocalInt3=0;
&& char& *
pMalloc1,*pMalloc2,*pMalloc3;
char& * pNew1,*pNew2,*pNew3;
&& printf("全局常量的内存地址\n");
printf("iGlobalConstInt1=0xx\n",&iGlobalConstInt1);
printf("iGlobalConstInt2=0xx\n",&iGlobalConstInt2);
printf("iGlobalConstInt3=0xx\n\n",&iGlobalConstInt3);
printf("iConstStaticInt1=0xx\n",&iConstStaticInt1);
printf("iConstStaticInt2=0xx\n",&iConstStaticInt2);
printf("iConstStaticInt3=0xx\n\n",&iConstStaticInt3);
&& printf("全局变量的内存地址\n");
printf("iGlobalInt1=0xx\n",&iGlobalInt1);
printf("iGlobalInt2=0xx\n",&iGlobalInt2);
printf("iGlobalInt3=0xx\n\n",&iGlobalInt3);
&& printf("静态变量的内存地址\n");
printf("iGlobalStaticInt1=0xx\n",&iGlobalStaticInt1);
printf("iGlobalStaticInt2=0xx\n",&iGlobalStaticInt2);
printf("iGlobalStaticInt3=0xx\n\n",&iGlobalStaticInt3);
printf("iStaticInt1=0xx\n",&iStaticInt1);
printf("iStaticInt2=0xx\n",&iStaticInt2);
printf("iStaticInt3=0xx\n\n",&iStaticInt3);
&& printf("本地变量的内存地址\n");
printf("iConstInt1=0xx\n",&iConstInt1);
printf("iConstInt2=0xx\n",&iConstInt2);
printf("iConstInt3=0xx\n\n",&iConstInt3);
printf("iLocalInt1=0xx\n",&iLocalInt1);
printf("iLocalInt2=0xx\n",&iLocalInt2);
printf("iLocalInt3=0xx\n\n",&iLocalInt3);
funcParamTest(iLocalInt1,iLocalInt2,iLocalInt3);
//在堆上分配内存,使用new
&& pNew1=new char[16];
&& pNew2=new char[16];
&& pNew3=new char[16];
//在堆上分配内存,使用malloc
&& pMalloc1 = (char *)malloc(
&& pMalloc2 = (char *)malloc(
&& pMalloc3 = (char *)malloc(
printf("在堆上分配内存内存地址\n");
printf("pMalloc1=0xx\n",pMalloc1);
printf("pMalloc2=0xx\n",pMalloc2);
printf("pMalloc3=0xx\n\n",pMalloc3);
&&& //释放new
分配的内存空间
&&& delete []
delete [] pNew2;
delete [] pNew3;
pNew1=NULL;
pNew2=NULL;
pNew3=NULL;
//释放malloc分配的内存空间
&& free(pMalloc1);
&& free(pMalloc2);
&& free(pMalloc3);
&& pMalloc1=NULL;
&& pMalloc2=NULL;
&& pMalloc3=NULL;
&& return 0;
本程序在Windows XP 下,VC6编译后的执行结果是:
注意,上面我们输出的完全是内存地址,也就是说,是程序在进程中内存地址(注意是虚拟内存地址而不是物理内存地址)。我们认真观察程序输出,发现每种类型的内存地址都是连续的,而不同类型之间内存地址有的是连续的,有的差别极大(注意:不同编译器可能输出的结果不一样,但这并不影响我们分析问题)。基本上,我们可以把这些地址范围分为如下几个部分:堆、栈、全局/静态存储区和常量存储区。
栈,就是那些由编译器在需要的时候分配,在不需要的时候自动释放的存储区。里面的变量通常是局部变量、函数参数等。在栈上分配内存,通常是指在执行函数时,函数内局部变量在栈上创建,函数执行结束时这被自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆,就是那些由new或使用malloc分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new/malloc就要对应一个delete/free。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。动态内存的生存期由我们决定,使用非常灵活,但问题最多,也是我们本章讨论的重点。
全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。静态存储区在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。
常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(通过特殊手段当然是可以修改的,例如Windows下可直接修改PE文件)。
通过分析上面的程序,我们大抵可以绘出程序内存分配情况
静态数据区
低端内存区域
高端内存区域
常量存储区
通过上面分析,我们知道,全局常量(例如iGlobalConstInt1)和局部静态常量(例如iConstStaticInt1)位于常量存储区;全局变量(例如iGlobalInt1)和局部静态变量(iStaticInt1)位于静态数据区;本地变量(例如iLocalInt1)和函数参数变量(例如iFuncParam1)位于栈区,它是动态存储区的一部分;使用malloc/new(例如pMalloc1,pNew1)分配的内存位于堆区,它也是动态存储区的一部分。
由于常量存储区和静态数据区都是在程序编译的时候就分配好空间了,而堆栈是在程序运行过程中自动分配好的空间。使用堆分配内存是显式分配的,我们在稍后详细介绍。下面简单介绍一下使用栈分配内存空间的原理。
我们来看看我们DEMO程序对于函数参数那一部分,我们发现,栈分配如下所示
iLocalInt2 = 0x0012ff3c
iLocalInt3 = 0x0012ff40
iFuncParam2 = 0x0012ff4c
低端内存区域
高端内存区域
iFuncParam1 = 0x0012ff48
iFuncParam3 = 0x0012ff50
iLocalInt1 = 0x0012ff38
我们发现函数参数地址和变量地址分布如上,其中多了四个字节,正好就是RET指令。首先,三个参数以从又到左的次序压入堆栈,先压“iFuncParam3”,再压“iFuncParam2”,最后压入“iFuncParam1”;然后压入函数的返回地址(RET),接着跳转到函数地址接着执行。第三步,将栈顶(ESP)减去一个数,为本地变量分配内存空间,接着就初始化本地变量的内存空间。感兴趣的读者可以使用工具反汇编上面的代码,然后就可以看到C语言是如何编译的了。
从上面我们可以看出,对于栈分配内存而言,是由编译器自动管理,无需我们手工控制。然而,对于堆而言,内存分配与释放由程序员控制了,方法非常灵活,但也最容易出现问题。
1-2&&&&&&&&&&
C语言内存管理
上一节中我们简单分析了C语言内存管理方式,其中对于堆的管理,我们仅进行了简单描述。在本节中我们要详细描述C语言内存管理。在介绍之前,我们需要先熟悉C语言堆内存管理涉及的函数。
void * alloca( size_t size );
alloca的作用分配一块大小为size个字节的可用内存块,并返回首地址。不能分配的时候返回NULL。alloca是从栈中分配内存空间,使用alloca分配内存后不必使用free来释放内存。alloca是分配一块未经初始化的内存,这和
malloc 一样,如果需要初始化可以调用 memset
函数。在上节中,我们已经对栈分配做了详细的描述,栈的释放是由编译器自动管理的,所以不需要我们手动去释放它。
示范代码如下:
#include &stdio.h&
#include &malloc.h&
int main()
size = 1000;
errcode = 0;
void&&& *pData =
&&& if (size
& 0 && size
pData = alloca( size );
if(pData != NULL)
&&& printf_s(
"Allocated %d bytes of stack at 0x%p",size, pData);
&&& printf_s(
"Allocated %d bytes of stack failed",size);
printf_s("Tried to allocate too many
bytes.\n");&&&&&&&
malloc 与 free
void * malloc( size_t size
void &&&free(
void *ptr );
malloc的作用分配一块大小为size个字节的可用内存块,并返回首地址。不能分配的时候返回NULL。malloc
是从堆中分配内存空间,使用malloc 分配内存后必须使用free释放内存。
free清除ptr所指向的地址,它只作清除的工作,并告诉系统,这块地址已经被释放和清除,可以重新被分配。使用malloc
分配的内存没有进行初始化,也就是说,该内存区中可能存在先前内容,而calloc 则将内存初始化为0。如果需要对malloc
分配的内存初始化,可以使用memset 函数。
示范代码如下:
&stdlib.h&&&&&&&&&
#include &stdio.h&
#include &malloc.h&
void main( void )
&& string = malloc( _MAX_PATH
&& // In a C++ file,
explicitly cast malloc's return.& For example,
&& // string = (char *)malloc(
_MAX_PATH );
&& if( string == NULL )
printf( "Insufficient memory available\n" );
printf( "Memory space allocated for path name\n" );
free( string );
printf( "Memory freed\n" );
void * calloc( size_t nmemb,size_t size);
calloc的作用是分配并初始化内存块,返回一个指向nmemb块数组的指针,每块大小为size个字节。它和malloc的主要不同之处是会初始化(清零)分配到的内存。
示范代码如下:
#include &stdio.h&
#include &malloc.h&
void main( void )
&& buffer = (long *)calloc(
40, sizeof( long ) );
&& if( buffer != NULL )
printf( "Allocated 40 long integers\n" );
printf( "Can't allocate memory\n" );
&& free( buffer );
void * realloc( void *ptr, size_t size );
realloc以ptr所指地址为首址,分配size个字节的内存,并返回ptr所指地址。realloc不会初始化分配到的内存块,如果ptr为NULL则相当于malloc,如果size为NULL则相当于free(ptr)。不能分配返回NULL。
示范代码如下:
#include &stdio.h&
#include &malloc.h&
#include &stdlib.h&
void main( void )
&& if( (buffer = (long
*)malloc( 1000 * sizeof( long ) )) == NULL )
exit( 1 );
&& size = _msize( buffer
&& printf( "Size of block
after malloc of 1000 longs: %u\n", size );
&& if( (buffer = realloc(
buffer, size + (1000 * sizeof( long )) ))
==& NULL )
exit( 1 );
&& size = _msize( buffer
&& printf( "Size of block
after realloc of 1000 more longs: %u\n",
&&&&&&&&&&&
&& free( buffer );
&& exit( 0 );
通过上面的学习,我们知道:alloca、calloc、malloc、realloc 负责分配内存,free 负责释放内存。其中
alloca 是在栈中分配内存,而calloc、malloc、realloc 是在堆中分配内存,也就是说 alloca
的内存分配,是有作用域的,不需要释放,而calloc、malloc、realloc内存是没有作用域的,需要调用 free
主动释放分配内存区域。alloca,malloc,realloc只负责分配内存,并不初始化分配内存空间,而calloc不仅分配内存,还负责初始化分配内存为0。realloc
是以传入指针为基址,分配指定大小的内存区域。
当读者阅读到此时的时候,可能觉得内存管理其实很简单,无非是分配内存释放内存而已。大家不妨看看如下一个程序:
void MyGetMemory(int iSize)
&& char * szTemp=(char
*)malloc(iSize);
if(!GetString(szTemp,iSize))
printf("getstring failed!\n");
&& free(szTemp);
相信大家能很快发现上面在GetString
函数返回失败的情况下,内存没有释放,将产生内存泄露。如果我们再更改一下,可能这个错误稍微隐蔽一点。
char * MyGetMemory(int iSize)
&& char * szTemp=(char
*)malloc(iSize);
if(!GetString(szTemp,iSize))
printf("getstring failed!\n");
return NULL;
void Test()
szMalloc=MyGetMemory(23);
&& if(szMalloc)
printf("out : %s \n",szMalloc);
free(szMalloc);
szMalloc=NULL;
这个程序的内存泄露同样是在GetString 失败的时候产生,我们单存分析Test
函数是发现不了内存泄露的。在实际项目中,由于较为复杂,可能忘记释放内容了,也有可能释放内容后再次释放内容等等,这些错误要么是程序运行时间越久,所耗内存越大,要么直接出现异常。如果分配了内存忘记释放,那样就产生了内存泄漏。为了防止内存泄漏,一些项目甚至要求对分配、释放内存进行跟踪,以避免内存泄漏。最简单的方法就是封装内存分配和释放函数,实际分配中并不直接调用alloca、calloc、malloc、realloc来分配内存,也不直接调用函数free来释放内存。另外,在服务器上,由于程序需要长期执行,频繁的分配内存资源会导致内存碎片的增多,这样可以使用内存池来解决这些问题。
既然内存管理错误这么频繁,后果这么严重,那么作为一个新手程序应该如何来避免这些问题呢?在下一节我们将详细介绍。
1-3&&&&&&&&&&
C语言内存使用要点及常见错误
在介绍内存使用要点之前,我们先看看使用C语言内存管理中经常出现的错误,尤其是新手。
1、内存分配后没有校验,使得内存未成功,却正常使用。
2、内存分配成功后,没有初始化就使用。
3、内存分配成功,也进行了初始化,可是使用内存时出现了越界(缓冲区溢出)。这种错误如果被黑客成功利用,最严重的后果就是整个操作系统被黑客完全控制。
4、内存分配成功后,忘记释放内存,产生内存泄漏。
5、内存分配成功后,也正常释放内存,可是在释放内存后又继续使用。
6、混淆指针和数组。
上面的这些问题,不仅仅是新手容易犯,一个工作多年的老程序员依然可能犯这样的错误。如果有一天,您发现您的程序在debug下可以成功运行,可是在release下却不能成功运行,一种可能就是您的数据没有被初始化。如果有一天,您的程序出现一个访问一个非常内存地址的错误,那么你应该检查一下是否产生了越界错误等等。总而言之,上面的任何一种错误出现了,就极有可能不好定位错误,尤其是访问越界、释放后继续使用的错误。
内存分配后不校验直接使用主要是新手犯这种错误,由于没有意识到内存分配会不成功。这种问题的解决办法很简单,判断指针是否为空就可以了,在上节中的例子比比皆是,就不列举出来了。另外一种情况是函数的入参为空,可以使用assert(p!=NULL)
来简单检查,也可以使用if(p!=NULL) 来判断。这类错误只要养成好习惯,是完全可以避免的。
内存分配后没有初始化,这种情况也是属于粗心引起,也通常是新手犯的错误。从上节中我们知道,alloca,malloc,realloc是只负责分配内存而不负责初始化内存的,完全可以想象,不初始化直接使用会导致不可预知的错误。其实,内存没有初始化是出现所有内存分配的情况下,可能是全局的,也有可能出现在栈上,当然更多是出现在堆分配上。比如如下的例子就是一个堆分配后没有初始化出现的错误,其实在实际项目中,肯定没有这么明显,中途隔了很多代码,所以往往不容易发现。
for(iTimes=0;iTimes&20;iTimes++)
While(iTimes&0)
后来由于程序需要我们注释掉了那段for循环,结果变成了
While(iTimes&0)
当然上面的错误实在是太明显了。实际上这种例子主要是修改了程序的某个逻辑后才出现的,尤其是删出逻辑后。比如在c++类的构造删除中没有初始化成员指针变量,我们先前必须调用该类的某个函数来分配并初始化,可是后来我们去掉了这个函数,而我们在析构函数中还保留着该指针的释放,这样当然会导致错误。总之,不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
访问内存越界错误可以说是一类非常严重的错误,因为这种错误非常难找。黑客也通常利用这种漏洞来入侵系统。这种错误出现几率最常见的就是文件路径,我们可能想当然的认为文件名不会超过255,因为是我们自己的文件名,认为完全可以控制,可是由于种种原因我们的文件名大于了255,那么会产生什么样的错误呢?由于我们的文件名需要使用memcpy复制内容,那么有可能我们的一部份代码数据可能被覆盖了,会出现什么错误只有天知道,尤其是每次的执行流程未知,产生的后果每次都不一样,这样的错误通常是很难定位的。鉴于此,微软在vc2005中增加了一系列_l的函数,例如_sprintf_l
增加了缓冲区长度,以减少这类问题。也就是说,对于程序员,无论是新手还是老手,在使用内存拷贝等情况下一定要考虑越界问题。
常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL)
或if(p!=NULL)进行防错处理。
对于忘记释放内存的问题,也是一个比较热门的话题。出现这种情况的后果是,所占内存越来越大,系统越来越慢。尤其是对于服务器程序,由于需要长时间运行,所以内存泄漏就变得非常重要了。所以我们在程序设计时一定要保证malloc/free、new/delete成对使用。这类问题还有可能出现在函数中分配内存的场合。例子如下:
void getpathname(char * szPath)
&& if(szPath == NULL)
szPath=(char *)malloc(1024);
memcpy(szPath,”mycopybuffer”,11);
void callf()
&& char * szP
&& szPath=NULL;
&& getpathname (szPath);
&& if(szPath != NULL)
&& Free(szPath);
&& szPath=NULL;
对于上面这个例子,曾经有人还说过,我分配了,也释放了,可是就是有内存泄漏。不过更多的情况是发现数据不正确,还不知道怎么回事,其实就是对指针理解不够。对于上面的例子,解决办法通常有三个,一是直接返回指针,二是传入
char **,三是如果是c++可以传入 char *
&。另外,需要避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
释放后继续使用内存指针的问题也是一个非常严重的问题,如果加上逻辑复杂,测试不能遍历所有路径的话,极有可能成为一个小BUG。那么出现这种问题有哪些情况呢?
1、程序逻辑异常复杂,释放之后并没有将所有指向这块内存区域的指针清0。
2、在函数中返回了栈内存的指针或引用。
3、使用free或delete释放了内存后,没有将指针设置为NULL,导致产生“野指针”。还有就是存在多个这向该内存的指针,释放时没有将所有指向该内存地址的指针设置为NULL。
程序逻辑异常的例子不太好找,但最容易理解。避免这类情况的方法就是尽量避免多个指针同时指向一个内存地址空间。如果的确不可避免,一定要在释放内存后,将所有指向该地址的指针都设置成NULL。
函数种返回了栈内存地址或引用这个例子比较容易理解。如下:
char * GetTemp()
char szTemp[]=”hello”;
return szT
在上面的例子中,由于szTemp属于栈内分配内存,在函数执行完成后,将自动释放szTemp分配的栈内存,所以调用GetTemp
函数取得的指针指向内存地址是一个无效地址,该内存中存储内容是不可预见的。
使用“野指针”的例子比较好理解。如下:
void test()
pTemp = (char *) malloc(256);
if(pTemp != NULL)
strcpy(pTemp,”mytemp1”);
printf(“%s\n”,pTemp);
free(pTemp);
if(pTemp != NULL)
&&&&&strcpy(pTemp,”mytemp2”);
printf(“%s\n”,pTemp);
上面的例子在 strcpy(pTemp,”mytemp2”);
就会出错,因为此时内存已经释放了。我们上面的例子非常容易察觉这个问题。可是我们在实际工程项目中,由于代码量大,程序非常复杂,如果不养成一中良好的习惯,极有可能会出现上面的问题,而且还不太好定位。有人也许会说,我检查下
free函数不就可以了么。关键的问题是,那是您知道是没有将这个指针在释放时设置为
NULL。那么我们在实际项目中,如何杜绝“野指针”呢?
1、指针变量必须进行初始化。
char *p = NULL;
char *str = (char *) malloc(100);
2、指针被释放以后,必须将所有指向该块内存区域的指针全部设置为NULL。
3、指针指向的内容是栈分配的内存时,一定要注意作用域的问题。
void Test(void)
&& p=NULL;
 && char szTemp[]
=”hello”;
p=szT // 注意 szTemp 的生命期
 printf(“%s\n”, p); // p是“野指针”
另外,由于指针和数组在很多情况下可以互换,导致混淆,容易犯一些小小的错误。其实,数组要么在静态存储区被创建,要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有内容可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。下面列举几种容易出错的例子。
char arr[] = "demo";
arr[0] = 'W';
cout && arr
char *pPointer = "microsoft"; // 注意pPointer指向常量字符串
pPointer[0] = 'w'; // 编译器不能发现该错误
cout && pPointer
在上面的例子我们可以发现,数组内容是可以修改的,arr[0] = 'W'; 该句能够正常执行。而pPointer
指向的为一个常量字符串,内容是不可以修改的。pPointer[0] = 'w';
这句在实际运行中则会导致运行错误,而编译时不能发现这个错误。
char arr[] = "demodemo";
char *pointer =
cout&& sizeof(arr)
&& // 8字节
cout&& sizeof(pointer)
&& // 4字节
从上面的例子中可以看出sizeof可以计算出数组的容量(字节数),而不能计算出指针的容量,除非您在分配内存的时候记住它。
1-4&&&&&&&&&&
在Windows 下如何跟踪内存泄露
通过上面章节的学习,我们对内存泄露深恶痛绝,那么如何检查一个程序的内存泄露呢?先介绍一个最简单的方法就是使用VC
调式工具。首先我们来故意产生内存泄露。
1、我们创建一个memleak的支持MFC的工程,工程类型为 win32 Console
Application,如图所示,并单击“OK”按钮。
2、在接下来的项目中我们选择“An application that supports
MFC.”选择支持MFC的控制台程序。并单击“Finish”。
3、在接下来的界面中单击“ok”按钮完成工程创建。
我们修改memleak.cpp,程序如下:
#include "stdafx.h"
#include "memleak.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
/////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theA
//故意产生内存泄露的函数
void memleaktest()
&& char * szTemp= new
char[1024];
&& szTemp=(char
*)malloc(1024);
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
&& int nRetCode = 0;
&& // initialize MFC and print
and error on failure
(!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(),
// TODO: change error code to suit your needs
cerr && _T("Fatal Error: MFC
initialization failed") &&
nRetCode = 1;
// TODO: code your application's behavior here.
CString strH
strHello.LoadString(IDS_HELLO);
cout && (LPCTSTR)strHello
//调用故意产生内存泄露的函数
memleaktest();
&& return nRetC
此时,我们按F5,系统会问是否需要编译,选择是以后,执行程序。我们需要注意程序调试输出部分。如果您的VC没有输出,不仿在菜单栏上右键选择“output”。
读者已经从上面图中的输出部分看到:
Detected memory leaks!
Dumping objects -&
F:\2008\07\prj\memleak\memleak.cpp(22) : {60} normal block at
0x24 bytes long.
&&&&&&&&&&&&&&&&
& CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
我们发现了VC探测出了内存泄露,此时我们点击行:
F:\2008\07\prj\memleak\memleak.cpp(22) : {60} normal block at
0x00386F08
则出现我们产生内存泄露的详细地方。
也许朋友会问,这是怎么实现的呢?我们不仿来分析以下代码,大家一定要注意如下代码
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
在debug状态下,我们使用new分配内存的时候,实际上会调用 DEBUG_NEW,该宏是怎么定义的呢?在
Afx.h中我们找到了它的定义
// Memory tracking allocation
void* AFX_CDECL operator new(size_t nSize, LPCSTR
lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)
#if _MSC_VER &= 1200
void AFX_CDECL operator delete(void* p, LPCSTR lpszFileName, int
在Afxmem.cpp中我们找到了函数定义,如下:
//afxmem.cpp
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName,
int nLine)
return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName,
#ifdef _DEBUG
void* __cdecl operator new(size_t nSize, int nType, LPCSTR
lpszFileName, int nLine)
#ifdef _AFX_NO_DEBUG_CRT
&& UNUSED_ALWAYS(nType);
UNUSED_ALWAYS(lpszFileName);
&& UNUSED_ALWAYS(nLine);
&& return ::operator
new(nSize);
&& void* pR
#ifdef _AFXDLL
&& _PNH pfnNewHandler =
&& for (;;)
pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);
if (pResult != NULL)
#ifdef _AFXDLL
if (pfnNewHandler == _pfnUninitialized)
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
pfnNewHandler = pState-&m_pfnNewH
if (pfnNewHandler == NULL || (*pfnNewHandler)(nSize) == 0)
if (_afxNewHandler == NULL || (*_afxNewHandler)(nSize) == 0)
&& return pR
#endif //_DEBUG
鉴于本章的主旨,我们不再继续分析下去,不过我们知道了VC6.0 大抵是如何跟踪内存泄露的。
上面分析内存泄露的方法是常见手段,一般程序员都必须掌握的。下面再介绍一种将内存泄露的信息输出到日志文件的办法,该方法非常简单,如果再加上定时器的,则可以定时分析系统运行到现在存在有哪些内存没有释放,如何实现呢?我们不妨还是使用刚才故意产生内存泄露的例子。
1、打开 stdafx.h 添加后的代码如下(黑体部分是我们需要添加的代码):
!defined(AFX_STDAFX_H__C6C9B115_4D_17F87E13978E__)
#define AFX_STDAFX_H__C6C9B115_4D_17F87E13978E__
#if _MSC_VER & 1000
#pragma once
#endif // _MSC_VER & 1000
VC_EXTRALEAN&&&&
// Exclude rarely-used stuff from Windows headers
#include &afx.h&
&afxwin.h&&&&&&&&&
// MFC core and standard components
&afxext.h&&&&&&&&&
// MFC extensions
&afxdtctl.h&&&&
// MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
&afxcmn.h&&&&&&
// MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
#include &iostream&
#ifdef _DEBUG
//for memory leak check
#define _CRTDBG_MAP_ALLOC
//使生成的内存dump包含内存块分配的具体代码为止
#include&stdlib.h&
#include&crtdbg.h&
2、在程序执行的开始,设置内存跟踪及设置输出文件
代码如下:
HANDLE hLogF//声明日志文件句柄
//允许检查内存泄露
_CrtSetDbgFlag( _CRTDBG_REPORT_FLAG);
&& int nRetCode =
//创建日志文件
&& hLogFile =
CreateFile("c:\\memleak.log", GENERIC_WRITE,
FILE_SHARE_WRITE|FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
//将warn级别的内容都输出到文件(注意dump的报告级别即为warning)
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
//将日志文件设置为告警的输出文件
_CrtSetReportFile(_CRT_WARN, hLogFile);
3、在程序执行结尾,我们输出内存泄露
//Dump从程序开始运行到该时刻点,已分配而未释放的内存
_CrtDumpMemoryLeaks();
CloseHandle(hLogFile);
我们按ctrl-F5执行程序,然后打开c:\ memleak.log看到内容如下:
Detected memory leaks!
Dumping objects -&
F:\2008\07\prj\memleak\memleak.cpp(22) : {61} normal block at
0x024 bytes long.
&&&&&&&&&&&&&&&&
& CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
{50} normal block at 0x0 bytes long.
C&&&&&&&&&&&&&
& 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD
{49} normal block at 0x bytes long.
|L&&&&&&&&&&&&
& 14 7C 4C 10 16 00 00 00 00 00 00 00 00 00 00
{47} client block at 0x00421E50, subtype 0, 64 bytes long.
a CDynLinkLibrary object at $ bytes long
Object dump complete.
在实际项目中,尤其是我们看一个长期运行的服务程序的内存泄露情况,可使用定时器定时获取从程序开始执行到当前时刻,有多少内存没有释放,这可能就是使用单纯F5调试内存泄露所不具有的手段。
学会如何防止并检查内存泄漏,是一个合格的c/c++程序员必须具备的能力。但是由于内存泄漏是程序运行并满足一定条件时才会发生,直接从代码中查出泄漏原因的难度较大。而且检查内存泄露的方法和工具很多,上面仅仅介绍了最简单也是最基础检查内存泄露的方法。希望感兴趣的读者一定要深入下去。
1-5&&&&&&&&&&
Windows 内存管理简述
最后,在内存分配上,不同操作系统具有一些特定的API函数,使用这些函数可使内存分配更高效、安全。例如在windows
系统上,具有一系列的函数负责内存分配、释放和管理。
用于内存管理的函数
CopyMemory 将一块内存从位置拷贝到另外一个位置,该函数使用频率非常高。
FillMemory 将指定内存块内容填充为指定数据
GetWriteWatch 查找已经被写入虚拟内存区域的页面地址
GlobalMemoryStatus 获得关于系统当前对于物理内存和虚拟的内存的使用信息。
GlobalMemoryStatusEx 获得关于系统当前对于物理内存和虚拟的内存的使用信息。
IsBadCodePtr 决定调用进程是否拥有对指定地址内存的读操作权。
IsBadReadPtr 检验调用进程是否拥有对指定内存范围的读操作权。
IsBadStringPtr 检验调用进程是否拥有对指定字符串所在地址区域的读操作权。
IsBadWritePtr 检验调用进程是否拥有对指定内存范围的写操作权。
MoveMemory 将一块内存从一个位置移动到另外的位置。
ResetWriteWatch 为某片虚拟内存区域重置写跟踪状态。
ZeroMemory 用零值填充某片内存块,该函数也是一个经常使用的函数。
AWE(Address Windowing Extensions)
AllocateUserPhysicalPages
分配物理内存页面与进程的AWE区域建立或取消映射&
FreeUserPhysicalPages
释放先前由AllocateUserPhysicalPages函数分配的物理内存页面。&
MapUserPhysicalPages
映射在AWE区域内的指定地址分配的物理内存。&
MapUserPhysicalPagesScatter映射在AWE区域内的指定地址分配的物理内存。&
全局函数(global functions)
GlobalAlloc 从堆中分配指定字节数量的内存。&
GlobalDiscard 丢弃指定的全局内存块。
GlobalFlags 返回关于指定全局内存对象的信息&
GlobalFree 释放指定的全局内存对象。&
GlobalHandle 返回指定全局内存块的指针的句柄。&
GlobalLock 锁定一个全局内存对象并且返回指向该内存块第一个字节的指针。&
GlobalReAlloc 改变指定全局内存对象的大小和属性。&
GlobalSize 得到指定内存对象的当前大小。&
GlobalUnlock 减少对一个内存对象的锁定数量。&
本地(local)函数
LocalAlloc 从堆中分配指定数量的内存。&
LocalDiscard 丢弃指定的本地内存对象。
LocalFlags 返回关于指定本地内存对象的信息。&
LocalFree 释放指定的本地内存对象。&
LocalHandle 得到指向指定本地内存对象的指针的句柄。&
LocalLock 锁定本地内存对象并且返回指向该内存对象的第一个字节的指针。&
LocalReAlloc 改变指定本地内存对象的大小或者属性。&
LocalSize 返回指定本地内存对象的当前大小。&
LocalUnlock 减少对某内存对象的锁定数量。
GetProcessHeap 获得调用进程的堆的一个句柄。
GetProcessHeaps 获得调用进程所有有效的堆的句柄。
HeapAlloc 从堆中分配一块内存。
HeapCompact 尝试压紧指定的堆。
HeapCreate 创建一个堆对象。
HeapDestroy 销毁指定的堆对象。
HeapFree 释放一块从堆中分配的内存。
HeapLock 尝试获得与指定堆关联的锁定。
HeapQueryInformation 获得关于指定堆的资料。
HeapReAlloc 从堆中重新分配一块内存。
HeapSetInformation 为指定的堆设置堆信息。
HeapSize 获得一个在堆上的内存块的大小。
HeapUnlock 获得与指定堆相关联的一个锁定的所有者。
HeapValidate 尝试使指定的堆有效。
HeapWalk 枚举指定堆上的内存块。
虚拟内存函数
VirtualAlloc 保留或提交调用进程虚拟地址空间的某一区域的页面。
VirtualAllocEx 保留或提交调用进程虚拟地址空间的某一区域的页面。
VirtualFree 释放或取消提交调用进程虚拟地址空间的某一区域的页面。
VirtualFreeEx 释放或取消提交调用进程虚拟地址空间的某一区域的页面。
VirtualLock 锁定指定的进程虚拟地址空间的指定块到物理内存中。
VirtualProtect 改变调用进程虚拟地址空间已提交页面区域的访问限制级。
VirtualProtectEx 改变调用进程虚拟地址空间已提交页面区域的访问限制级。
VirtualQuery 提供关于调用进程虚拟地址空间页面区域的资料。
VirtualQueryEx 提供关于调用进程虚拟地址空间页面区域的资料。
VirtualUnlock 对某进程虚拟地址空间的某区域的页面解锁。
限于本章主旨,上面函数系列不再深入介绍,感兴趣的读者可以去深入了解。
1-6&&&&&&&&&&
总结和建议读者的练习
1、使用 OLLYDBG 反汇编分析C语言内存管理。
2、包装内存分配和释放的函数,以探测内存泄露。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 c语言 的文章

 

随机推荐