C语言内存管理(三)

C语言内存管理(三)
2011年04月15日
  
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__;
  #endif
  /
  // The one and only application object
  CWinApp theApp;
  using namespace std;
  //故意产生内存泄露的函数
  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
  if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
  {
  // TODO: change error code to suit your needs
  cerr
  F:\2008\07\prj\memleak\memleak.cpp(22) : {60} normal block at 0x00386F08, 1024 bytes long.
  Data: CD 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__;
  #endif
  在debug状态下,我们使用new分配内存的时候,实际上会调用 DEBUG_NEW,该宏是怎么定义的呢?在Afx.h中我们找到了它的定义
  //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 nLine);
  #endif
  在Afxmem.cpp中我们找到了函数定义,如下:
  //afxmem.cpp
  void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
  {
  return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
  }
  #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);
  #else
  void* pResult;
  #ifdef _AFXDLL
  _PNH pfnNewHandler = _pfnUninitialized;
  #endif
  for (;;)
  {
  pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);
  if (pResult != NULL)
  return pResult;
  #ifdef _AFXDLL
  if (pfnNewHandler == _pfnUninitialized)
  {
  AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
  pfnNewHandler = pState->m_pfnNewHandler;
  }
  if (pfnNewHandler == NULL || (*pfnNewHandler)(nSize) == 0)
  break;
  #else
  if (_afxNewHandler == NULL || (*_afxNewHandler)(nSize) == 0)
  break;
  #endif
  }
  return pResult;
  #endif
  }
  #endif //_DEBUG
  鉴于本章的主旨,我们不再继续分析下去,不过我们知道了VC6.0 大抵是如何跟踪内存泄露的。
  上面分析内存泄露的方法是常见手段,一般程序员都必须掌握的。下面再介绍一种将内存泄露的信息输出到日志文件的办法,该方法非常简单,如果再加上定时器的,则可以定时分析系统运行到现在存在有哪些内存没有释放,如何实现呢?我们不妨还是使用刚才故意产生内存泄露的例子。
  1、 打开 stdafx.h 添加后的代码如下(黑体部分是我们需要添加的代码):
  #if !defined(AFX_STDAFX_H__C6C9B115_8277_4302_914D_17F87E13978E__)
  #define AFX_STDAFX_H__C6C9B115_8277_4302_914D_17F87E13978E__
  #if _MSC_VER > 1000
  #pragma once
  #endif // _MSC_VER > 1000
  #define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
  #include
  #include // MFC core and standard components
  #include // MFC extensions
  #include // MFC support for Internet Explorer 4 Common Controls
  #ifndef _AFX_NO_AFXCMN_SUPPORT
  #include // MFC support for Windows Common Controls
  #endif // _AFX_NO_AFXCMN_SUPPORT
  #include
  #ifdef _DEBUG
  //for memory leak check
  #define _CRTDBG_MAP_ALLOC //使生成的内存dump包含内存块分配的具体代码为止
  #include
  #include
  #endif
  #endif
  2、 在程序执行的开始,设置内存跟踪及设置输出文件
  代码如下:
  HANDLE hLogFile;//声明日志文件句柄
  //允许检查内存泄露
  _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG);
  {
  int nRetCode = 0;
  //创建日志文件
  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 0x00386EB8, 1024 bytes long.
  Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
  {50} normal block at 0x00421CF0, 33 bytes long.
  Data: 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD
  {49} normal block at 0x00421D40, 40 bytes long.
  Data: 14 7C 4C 10 16 00 00 00 00 00 00 00 00 00 00 00
  {47} client block at 0x00421E50, subtype 0, 64 bytes long.
  a CDynLinkLibrary object at $00421E50, 64 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、 包装内存分配和释放的函数,以探测内存泄露。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值