内存泄漏以及常见的解决方法

2022-07-12 17:27:26 浏览数 (1)

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

代码语言:javascript复制
  之所以撰写这篇文章是由于前段时间花费了非常大的精力在已经成熟的代码上再去处理memory leak问题。写此的目的是希望我们应该养成良好的编码习惯,尽可能的避免这种问题,由于当你对着一大片的代码再去处理此类的问题,此时无疑添加�了解决的成本和难度。准确的说属于补救措施了。
代码语言:javascript复制
1. 什么是内存泄漏(memory leak)?
代码语言:javascript复制
 指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 

A memory leak is a particular type of unintentional memory consumption by a computer program where the program fails to release memory when no longer needed. This condition is normally the result of a bug in a program that prevents it from freeing up memory that it no longer needs.This term has the potential to be confusing, since memory is not physically lost from the computer. Rather, memory is allocated to a program, and that program subsequently loses the ability to access it due to program logic flaws.

代码语言:javascript复制
2. 对于C和C  这样的没有Garbage Collection 的语言来讲,我们主要关注两种类型的内存泄漏:

堆内存泄漏(Heap leak)。对内存指的是程序执行中依据须要分配通过malloc,realloc new等从堆中分配的一块内存,再是完毕后必须通过调用相应的 free或者delete 删掉。假设程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.

代码语言:javascript复制
  系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比方 Bitmap,handle ,SOCKET等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。  
代码语言:javascript复制
3. 怎样解决内存泄露?
代码语言:javascript复制
内存泄露的问题其困难在于1.编译器不能发现这些问题。2.执行时才干捕获到这些错误,这些错误没有明显的症状,时隐时现。3.对于手机等终端开发用户来说,尤为困难。以下从三个方面来解决内存泄露:
代码语言:javascript复制
第一,良好的编码习惯,尽量在涉及内存的程序段,检測出内存泄露。当程式稳定之后,在来检測内存泄露时,无疑添加�了排除的困难和复杂度。
代码语言:javascript复制
使用了内存分配的函数,要记得要使用其想用的函数释放掉,一旦使用完成。
代码语言:javascript复制
Heap memory:
代码语言:javascript复制
mallocrealloc ------  free
代码语言:javascript复制
new new[] ----------  delete delete[]
代码语言:javascript复制
GlobalAlloc------------GlobalFree 
代码语言:javascript复制
要特别注意数组对象的内存泄漏
代码语言:javascript复制
     MyPointEX *pointArray =new MyPointEX [100];
代码语言:javascript复制
      其删除形式为:
代码语言:javascript复制
     delete []pointArray 
代码语言:javascript复制
Resource Leak :对于系统资源使用之前要细致看起用法,防止错误使用或者忘记释放掉系统资源。
代码语言:javascript复制
我们看MSDN上一个创建字体的样例:
代码语言:javascript复制
 RECT rect;
代码语言:javascript复制
HBRUSH hBrush;
代码语言:javascript复制
FONT hFont;
代码语言:javascript复制
hdc = BeginPaint(hWnd, &ps);
代码语言:javascript复制
 hFont = reateFont(48,0,0,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Impact"));
代码语言:javascript复制
SelectObject(hdc, hFont); 
代码语言:javascript复制
SetRect(&rect, 100,100,700,200);
代码语言:javascript复制
SetTextColor(hdc, RGB(255,0,0));

DrawText(hdc, TEXT(“Drawing Text with Impact”), -1,&rect, DT_NOCLIP);

代码语言:javascript复制
DeleteObject(hFont);  
代码语言:javascript复制
 EndPaint(hWnd, &ps);
代码语言:javascript复制
假设使用完毕时候忘记释放字体,就造成了资源泄漏。 
代码语言:javascript复制
   对于基于引用计数的系统对象尤其要注意,由于仅仅有其引用计数为0时,该对象才干正确被删除。而其使用过程中有其生成的新的系统资源,使用完成后,假设没有及时删除,都会影响其引用计数。
代码语言:javascript复制
 IDNS *m_pDns//define a DNS object.
代码语言:javascript复制
   If(NULL == m_pDns)
代码语言:javascript复制
{  
代码语言:javascript复制
   IEnv_CreateInstance (m_pEnv,AEECLSID_DNS,(void **) (&m_pDns))

}

代码语言:javascript复制
 If(m_pDns)
代码语言:javascript复制
{

Char szbuff[256];

IDNS_AddQuestions(M_pDns,AEEDNSTYPE_A,ADDDNSCLASS_IN,szbuff);

IDNS_Start(m_pDns,this);

const AEEDNSResponse * pDnsResponse = NULL;

IDNS_GetResponse(pMe->m_pDns, &pDnsResponse);

代码语言:javascript复制
…………………………………………………………

…………………………………………………………..

………………………………………………………..

}

代码语言:javascript复制
DNS_Release(pMe->m_pDns);//当程序执行到此时,其返回值不是0,是1,其含义是程序已经产生内存泄露了,系统已经有一个由DNS所产生的内核对象没有释放,而当这段代码多次执行之后,内存泄露将不断添加�……..

m_pDns=NULL;

}

代码语言:javascript复制
看起来非常不直观,细致分析就会发现,对象pDnsResponse是从m_pDns产生新的object,所以m_pDns的引用计数会添加�,因此在使用完pDnsResponse,应该release 该对象使其引用计数恢复正常。
代码语言:javascript复制
对于资源,也可使用RAII,RAII(Resource acquisition is initialization)资源获取即初始化,它是一项非常easy的技术,利用C  对象生命周期的概念来控制程序的资源,比如内存,文件句柄,网络连接以及审计追踪(audit trail)等.RAII的基本技术原理非常easy.若希望保持对某个重要资源的跟踪,那么创建一个对象,并将资源的生命周期和对象的生命周期相关联.如此一来,就能够利用C  复杂老练的对象管理设施来管理资源.(有待完好) 
代码语言:javascript复制
例2: 
代码语言:javascript复制
Struct ITypeface *pTypeface;
代码语言:javascript复制
if (pTypeface)
代码语言:javascript复制
{
代码语言:javascript复制
IANY_CreateInstance(g_pApplet->m_pIShell,AEECLSID_BTFETypeface,void**)& Typeface);
代码语言:javascript复制
} 
代码语言:javascript复制
接下来我们就能够从这个接口上面创建字体,比方
代码语言:javascript复制
IHFont **pihf=NULL;
代码语言:javascript复制
   ITypeface_NewFontFromFile(ITypeface,……,&pihf).
代码语言:javascript复制
   ITypeface_NewFontFrommemory(ITypeface,……..,&pihf)
代码语言:javascript复制
   ITypeface_NewFontFromClassID(IType,……,&pihf)
代码语言:javascript复制
   可是要切记,这些字体在使用完毕后一定要release掉,否则最后 iTypeface的引用计数就是你最后没有删除掉的字体的个数。 
代码语言:javascript复制
第二,重载  new 和 delete。这也是大家编码过程中常用的方法。
代码语言:javascript复制
以下给出简单的sample来说明。
代码语言:javascript复制
memchecker.h
代码语言:javascript复制
structMemIns
代码语言:javascript复制
{
代码语言:javascript复制
    void * pMem;
代码语言:javascript复制
    int m_nSize;
代码语言:javascript复制
    char m_szFileName[256];
代码语言:javascript复制
    int m_nLine;
代码语言:javascript复制
    MemIns * pNext;
代码语言:javascript复制
};
代码语言:javascript复制
classMemManager
代码语言:javascript复制
{
代码语言:javascript复制
public:
代码语言:javascript复制
    MemManager();
代码语言:javascript复制
    ~MemManager();
代码语言:javascript复制
private:
代码语言:javascript复制
    MemIns *m_pMemInsHead;
代码语言:javascript复制
    int m_nTotal;
代码语言:javascript复制
public:
代码语言:javascript复制
    static MemManager* GetInstance();
代码语言:javascript复制
    void Append(MemIns *pMemIns);
代码语言:javascript复制
    void Remove(void *ptr);
代码语言:javascript复制
    void Dump(); 
代码语言:javascript复制
};
代码语言:javascript复制
void *operatornew(size_tsize,constchar*szFile, int nLine);
代码语言:javascript复制
void operatordelete(void*ptr,constchar*szFile, int nLine);
代码语言:javascript复制
 void operatordelete(void*ptr);
代码语言:javascript复制
void*operatornew[] (size_tsize,constchar*szFile,int nLine);
代码语言:javascript复制
void operatordelete[](void*ptr,constchar*szFile, int nLine);
代码语言:javascript复制
void operatordelete[](void *ptr);
代码语言:javascript复制
memechecker.cpp
代码语言:javascript复制
#include"Memchecher.h"
代码语言:javascript复制
#include<stdio.h>
代码语言:javascript复制
#include<malloc.h>
代码语言:javascript复制
#include<string.h>
代码语言:javascript复制
MemManager::MemManager()
代码语言:javascript复制
{
代码语言:javascript复制
    m_pMemInsHead=NULL;
代码语言:javascript复制
    m_nTotal=NULL;
代码语言:javascript复制
}
代码语言:javascript复制
MemManager::~MemManager()
代码语言:javascript复制
{
代码语言:javascript复制
}
代码语言:javascript复制
voidMemManager::Append(MemIns *pMemIns)
代码语言:javascript复制
{
代码语言:javascript复制
    pMemIns->pNext=m_pMemInsHead;
代码语言:javascript复制
    m_pMemInsHead = pMemIns;
代码语言:javascript复制
    m_nTotal = m_pMemInsHead->m_nSize;
代码语言:javascript复制
}
代码语言:javascript复制
voidMemManager::Remove(void *ptr)
代码语言:javascript复制
{
代码语言:javascript复制
    MemIns * pCur = m_pMemInsHead;
代码语言:javascript复制
    MemIns * pPrev = NULL;
代码语言:javascript复制
    while(pCur)
代码语言:javascript复制
    {
代码语言:javascript复制
        if(pCur->pMem ==ptr)
代码语言:javascript复制
        {
代码语言:javascript复制
           if(pPrev)
代码语言:javascript复制
            {
代码语言:javascript复制
               pPrev->pNext =pCur->pNext;
代码语言:javascript复制
            }
代码语言:javascript复制
           else
代码语言:javascript复制
            {
代码语言:javascript复制
               m_pMemInsHead =pCur->pNext;
代码语言:javascript复制
            }
代码语言:javascript复制
           m_nTotal-=pCur->m_nSize;
代码语言:javascript复制
           free(pCur);
代码语言:javascript复制
           break;
代码语言:javascript复制
        }
代码语言:javascript复制
        pPrev = pCur;
代码语言:javascript复制
        pCur = pCur->pNext;
代码语言:javascript复制
    }
代码语言:javascript复制
}
代码语言:javascript复制
voidMemManager::Dump()
代码语言:javascript复制
{
代码语言:javascript复制
    MemIns * pp = m_pMemInsHead;
代码语言:javascript复制
    while(pp)
代码语言:javascript复制
    {
代码语言:javascript复制
        printf( "File is %sn", pp->m_szFileName );
代码语言:javascript复制
        printf( "Size is %dn", pp->m_nSize );
代码语言:javascript复制
        printf( "Line is %dn", pp->m_nLine );
代码语言:javascript复制
        pp = pp->pNext;
代码语言:javascript复制
    }
代码语言:javascript复制
}
代码语言:javascript复制
代码语言:javascript复制
voidPutEntry(void *ptr,intsize,constchar*szFile, int nLine)
代码语言:javascript复制
{
代码语言:javascript复制
    MemIns * p = (MemIns *)(malloc(sizeof(MemIns)));
代码语言:javascript复制
    if(p)
代码语言:javascript复制
    {
代码语言:javascript复制
        strcpy(p->m_szFileName,szFile);
代码语言:javascript复制
        p->m_nLine = nLine;
代码语言:javascript复制
        p->pMem = ptr;
代码语言:javascript复制
        p->m_nSize = size;
代码语言:javascript复制
        MemManager::GetInstance()->Append(p);
代码语言:javascript复制
    }
代码语言:javascript复制
}
代码语言:javascript复制
voidRemoveEntry(void *ptr)
代码语言:javascript复制
{
代码语言:javascript复制
    MemManager::GetInstance()->Remove(ptr);
代码语言:javascript复制
}
代码语言:javascript复制
void *operatornew(size_tsize,constchar*szFile, int nLine)
代码语言:javascript复制
{
代码语言:javascript复制
    void * ptr = malloc(size);
代码语言:javascript复制
    PutEntry(ptr,size,szFile,nLine);
代码语言:javascript复制
    return ptr;
代码语言:javascript复制
}
代码语言:javascript复制
voidoperatordelete(void *ptr)
代码语言:javascript复制
{
代码语言:javascript复制
    RemoveEntry(ptr);
代码语言:javascript复制
    free(ptr);
代码语言:javascript复制
}
代码语言:javascript复制
void operatordelete(void*ptr,constchar * file, intline)
代码语言:javascript复制
{
代码语言:javascript复制
    RemoveEntry(ptr);
代码语言:javascript复制
    free(ptr);
代码语言:javascript复制
}
代码语言:javascript复制
void*operatornew[] (size_tsize,constchar* szFile,intnLine)
代码语言:javascript复制
{
代码语言:javascript复制
    void * ptr = malloc(size);
代码语言:javascript复制
    PutEntry(ptr,size,szFile,nLine);
代码语言:javascript复制
    return ptr;
代码语言:javascript复制
}
代码语言:javascript复制
void operatordelete[](void *ptr)
代码语言:javascript复制
{
代码语言:javascript复制
    RemoveEntry(ptr);
代码语言:javascript复制
    free(ptr);
代码语言:javascript复制
}
代码语言:javascript复制
void operatordelete[](void*ptr,constchar*szFile,intnLine)

{

RemoveEntry(ptr);

代码语言:javascript复制
    free(ptr);
代码语言:javascript复制
}
代码语言:javascript复制
#definenewnew(__FILE__,__LINE__)
代码语言:javascript复制
MemManagerm_memTracer;
代码语言:javascript复制
MemManager*MemManager::GetInstance()
代码语言:javascript复制
{
代码语言:javascript复制
    return &m_memTracer;
代码语言:javascript复制
} 
代码语言:javascript复制
void main()

{

代码语言:javascript复制
    int *plen =newint ;
代码语言:javascript复制
    *plen=10;
代码语言:javascript复制
    delete plen;
代码语言:javascript复制
    char *pstr=newchar[35];
代码语言:javascript复制
    strcpy(pstr,"hello memory leak");
代码语言:javascript复制
    m_memTracer.Dump();
代码语言:javascript复制
    return ;
代码语言:javascript复制
}
代码语言:javascript复制
 其主要思路是将分配的内存以链表的形式自行管理,使用完成之后从链表中删除,程序结束时可检查改链表,当中记录了内存泄露的文件,所在文件的行数以及泄露的大小哦。
代码语言:javascript复制
第三,Boost 中的smart pointer(待完好,结合大家的建议)
代码语言:javascript复制
第四,一些常见的工具插件,详见我的Blog中相关文章。
代码语言:javascript复制
4. 由内存泄露引出内存溢出话题:
代码语言:javascript复制
所谓内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是会产生内存溢出的问题。
代码语言:javascript复制
常见的溢出主要有:
代码语言:javascript复制
内存分配未成功,却使用了它。
经常使用解决的方法是,在使用内存之前检查指针是否为NULL。假设指针p 是函数的參数,那么在函数的入口处用assert(p!=NULL)进行检查。假设是用malloc 或new 来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。
代码语言:javascript复制
内存分配尽管成功,可是尚未初始化就引用它。
内存分配成功而且已经初始化,但操作越过了内存的边界。
比如在使用数组时常常发生下标“多1”或者“少1”的操作。特别是在for 循环语句中,循环次数非常easy搞错,导致数组操作越界。
代码语言:javascript复制
使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
代码语言:javascript复制
程序中的对象调用关系过于复杂,实在难以搞清楚某个对象到底是否已经释放了内存,此时应该又一次设计数据结构,从根本上解决对象管理的混乱局面。(这点但是深有感受,呵呵)
代码语言:javascript复制
不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/118606.html原文链接:https://javaforall.cn

0 人点赞