Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1614552
  • 博文数量: 441
  • 博客积分: 20087
  • 博客等级: 上将
  • 技术积分: 3562
  • 用 户 组: 普通用户
  • 注册时间: 2006-06-19 15:35
文章分类

全部博文(441)

文章存档

2014年(1)

2012年(1)

2011年(8)

2010年(16)

2009年(15)

2008年(152)

2007年(178)

2006年(70)

分类: C/C++

2009-11-19 18:18:21

Windows编程内存泄漏是一个非常郁闷的问题, 如果用mfc有的时候还能检测到内存泄漏,但是如果用win32那就难检测到了。 当然, 如果是自己显式的内存分配, 即你在程序中很明显的用new, malloc之类的内存分配函数,或者用HeapAlloc, 那么找到这类的内存泄漏还不是太难搞定, 合理的设计就可以大大的避免泄漏的问题, 可是对于gdi的函数, 那可就郁闷了。

在windows中, gdi的所有Create函数, 都要用对应的Delete函数删除,如
CreatePen, CreateBrushIndirect, CreateRectRgnIndirect等函数, 必须用DeleteObject删除

对于GetDC, 也要用ReleaseDC及时的释放, 否则, 就会造成内存泄漏
尤其是, 这些函数都要用于画图, 这很可能在一个循环之中, 或者是在OnPaint或者OnDraw之类的调用非常频繁的函数中, 如果不及时释放, 会造成大量的内存泄漏,程序不久就挂掉了。

需要注意的是, 像GetDC, CreateBrushIndirect, CreatePen之类的函数, msdn会很明白的告诉你对应的释放或者删除函数是什么, 但是像CreateRectRgnIndirect之类的函数, 你看这名字,觉着应该有对应的删除函数, 可是该死的msdn却并不告诉你需要删除, 结果是, 你没有调用, 导致了大量的内存泄漏, 我就是因为这个问题, 一个程序调试了一天,最后才在vckbase上面找到答案()。

还有一点要注意的是, 在DeleteObject之前, 必须要用SelectObject把已经选进的设备环境的gdi对象再换出来, 否则, DeleteObject将失败, 同样会造成内存泄漏。

下面是带给我惨痛教训的一段代码:
(这段代码是我改写MFC中的CRectTracker类的代码)
void CRectTracker32::DrawDragRect(CDC* pDC, LPCRECT lpRect, SIZE size,
                  LPCRECT lpRectLast, SIZE sizeLast)
{
   
    // first, determine the update region and select it
    HRGN rgnNew = NULL;
    HRGN rgnOutside = NULL, rgnInside = NULL;
    rgnOutside = ::CreateRectRgnIndirect(lpRect);

    CRect rect = *lpRect;
    rect.InflateRect(-size.cx, -size.cy);
    rect.IntersectRect(rect, *lpRect);

    rgnInside = :: CreateRectRgnIndirect(rect);
    rgnNew = ::CreateRectRgn(0, 0, 0, 0);
    ::CombineRgn(rgnNew, rgnOutside, rgnInside, RGN_XOR);

    HRGN rgnLast = NULL;
    HRGN rgnUpdate = NULL;
    if (lpRectLast != NULL)
    {
        // find difference between new region and old region
        rgnLast = CreateRectRgn(0, 0, 0, 0);
        ::SetRectRgn(rgnOutside, lpRectLast->left,  lpRectLast->top,  
            lpRectLast->right, lpRectLast->bottom );

        rect = *lpRectLast;
        rect.InflateRect(-sizeLast.cx, -sizeLast.cy);
        rect.IntersectRect(rect, *lpRectLast);
        ::SetRectRgn(rgnInside, rect.left, rect.top, rect.right, rect.bottom);
        ::CombineRgn(rgnLast, rgnOutside, rgnInside, RGN_XOR);

        rgnUpdate = CreateRectRgn(0, 0, 0, 0);
        ::CombineRgn(rgnUpdate, rgnLast, rgnNew, RGN_XOR);
    }
   
    HDC hDC = pDC->GetHDC();
    // draw into the update/new region
    ::SelectClipRgn(hDC, (rgnUpdate != NULL) ? rgnUpdate : rgnNew);
    ::GetClipBox(hDC, &rect);

    HBRUSH hOldBrush = (HBRUSH)::SelectObject(hDC, m_hBrushs[0]);

    ::PatBlt(hDC, rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT);

    // cleanup DC
    ::SelectObject(hDC, hOldBrush);
    ::SelectClipRgn(hDC, NULL);

    // 我以为HRGN对象不需要删除,再加上这段代码是改自MFC, MFC中也没有删除
    // 所以,下面的代码没有调用,结果造成了大量的内存泄漏

    if ( rgnNew != NULL )
        ::DeleteObject(rgnNew);
    if ( rgnInside != NULL )
        ::DeleteObject(rgnInside);
    if ( rgnOutside != NULL )
        ::DeleteObject(rgnOutside);
    if ( rgnLast != NULL )
        ::DeleteObject(rgnLast);
    if ( rgnUpdate != NULL )
        ::DeleteObject(rgnUpdate);
   
}

总结一下, 对于自己显式的内存分配, 写完new, alloc, calloc, HeapAlloc之类的函数之后, 一定要马上写释放的代码, 用一个比较合理的结构来组织代码, 这样造成的内存泄漏要少很多;其次, 对于使用GDI函数, 创建GDI对象后, 一定要注意删除, 每个Get或者Create之类的GDI函数都要仔细的看看有没有对应的释放、删除函数, 要及时的调用。
阅读(2644) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~