??xml version="1.0" encoding="utf-8" standalone="yes"?> 在C语言中,假设我们有这L一个函敎ͼ int function(int a,int b) 调用时只要用result = function(1,2)q样的方式就可以使用q个函数。但是,当高U语a被编译成计算机可以识别的机器码时Q有一个问题就凸现出来Q在CPU中,计算机没有办法知道一个函数调用需要多个、什么样的参敎ͼ也没有硬件可以保存这些参数。也是_计算Z知道怎么l这个函C递参敎ͼ传递参数的工作必须由函数调用者和函数本n来协调。ؓ此,计算机提供了一U被UCؓ栈的数据l构来支持参C递?/p> 栈是一U先q后出的数据l构Q栈有一个存储区、一个栈指针。栈指针指向堆栈中W一个可用的数据(被称为栈Ӟ。用户可以在栈顶上方向栈中加入数据,q个操作被称为压?Push)Q压栈以后,栈顶自动变成新加入数据项的位|,栈顶指针也随之修攏V用户也可以从堆栈中取走栈顶Q称为弹出栈(pop)Q弹出栈后,栈顶下的一个元素变成栈Ӟ栈顶指针随之修改?/p> 函数调用Ӟ调用者依ơ把参数压栈Q然后调用函敎ͼ函数被调用以后,在堆栈中取得数据Qƈq行计算。函数计结束以后,或者调用者、或者函数本w修改堆栈,使堆栈恢复原装?/p> 在参C递中Q有两个很重要的问题必须得到明确说明Q?/p> 在高U语a中,通过函数调用U定来说明这两个问题。常见的调用U定有:
stdcall很多时候被UCؓpascal调用U定Q因为pascal是早期很常见的一U教学用计算机程序设计语aQ其语法严}Q用的函数调用U定是stdcall。在Microsoft C++pd的C/C++~译器中Q常常用PASCAL宏来声明q个调用U定Q类似的宏还有WINAPI和CALLBACK?/p> stdcall调用U定声明的语法ؓ(以前文的那个函数ZQ: int __stdcall function(int a,int b) stdcall的调用约定意味着Q?Q参C叛_左压入堆栈,2Q函数自w修改堆?3)函数名自动加前导的下划线Q后面紧跟一个@W号Q其后紧跟着参数的尺?/p> 以上q这个函Cؓ例,参数b首先被压栈,然后是参数aQ函数调用function(1,2)调用处翻译成汇编语言变成: 而对于函数自w,则可以翻译ؓQ? 而在~译Ӟq个函数的名字被译成_function@8
注意不同~译器会插入自己的汇~代码以提供~译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是~译器常用的Ҏ?/p> 从函数调用看Q??依次被pushq堆栈,而在函数中又通过相对于ebp(卛_q函数时的堆栈指针)的偏U量存取参数。函数结束后Qret 8表示清理8个字节的堆栈Q函数自己恢复了堆栈?/p> cdecl调用U定又称为C调用U定Q是C语言~省的调用约定,它的定义语法是: 在写本文ӞZ我的意料Q发现cdecl调用U定的参数压栈顺序是和stdcall是一LQ参数首先由有向左压入堆栈。所不同的是Q函数本w不清理堆栈Q调用者负责清理堆栈。由于这U变化,C调用U定允许函数的参数的个数是不固定的,q也是C语言的一大特艌Ӏ对于前面的function函数Q用cdecl后的汇编码变成: MSDN中说Q该修饰自动在函数名前加前导的下划线Q因此函数名在符可中被记录为_functionQ但是我在编译时g没有看到q种变化?/p> ׃参数按照从右向左序压栈Q因此最开始的参数在最接近栈顶的位|,因此当采用不定个数参数时Q第一个参数在栈中的位|肯定能知道Q只要不定的参数个数能够ҎW一个后者后l的明确的参数确定下来,可以用不定参敎ͼ例如对于CRT中的sprintf函数Q定义ؓQ?
int sprintf(char* buffer,const char* format,...) ׃所有的不定参数都可以通过format定Q因此用不定个数的参数是没有问题的?/p> fastcall调用U定和stdcallcMQ它意味着Q?
其声明语法ؓQint fastcall function(int a,int b) thiscall是唯一一个不能明指明的函数修饰Q因为thiscall不是关键字。它是C++cL员函数缺省的调用U定。由于成员函数调用还有一个this指针Q因此必ȝD处理,thiscall意味着Q?
Z说明q个调用U定Q定义如下类和用代码: callee函数被翻译成汇编后就变成Q? 可见Q对于参C数固定情况下Q它cM于stdcallQ不定时则类似cdecl q是一个很见的调用约定,一般程序设计者徏议不要用。编译器不会l这U函数增加初始化和清理代码,更特D的是,你不能用returnq回q回|只能用插入汇~返回结果。这一般用于实模式驱动E序设计Q假讑֮义一个求和的加法E序Q可以定义ؓQ? 注意Q这个函数没有显式的returnq回|q回通过修改eax寄存器实玎ͼ而且q退出函数的ret指o都必L式插入。上面代码被译成汇~以后变成: 注意q个修饰是和__stdcall及cdecll合使用的,前面是它和cdecll合使用的代码,对于和stdcalll合的代码,则变成: 至于q种函数被调用,则和普通的cdecl及stdcall调用函数一致?/p> 如果定义的约定和使用的约定不一_则将D堆栈被破坏,D严重问题Q下面是两种常见的问题:
以后者ؓ例,假设我们在dllU声明了一U函CؓQ? 使用时代码ؓQ? ׃调用者没有理解WINAPI的含义错误的增加了这个修饎ͼ上述代码必然D堆栈被破坏,MFC在编译时插入的checkesp函数告诉你Q堆栈被破坏了?/p>用约?
stdcall调用U定
push 2 W二个参数入?push 1 W一个参数入?call function 调用参数Q注意此时自动把cs:eip入栈
push ebp 保存ebp寄存器,该寄存器用来保存堆栈的栈顶指针Q可以在函数退出时恢复 mov ebp,esp 保存堆栈指针 mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 8
cdecl调用U定
int function (int a ,int b) //不加修饰是C调用U定 int __cdecl function(int a,int b)//明确指出C调用U定
调用?/b> push 1 push 2 call function add esp,8 注意Q这里调用者在恢复堆栈被调用函数_function?/b> push ebp 保存ebp寄存器,该寄存器用来保存堆栈的栈顶指针Q可以在函数退出时恢复 mov ebp,esp 保存堆栈指针 mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 注意Q这里没有修改堆?/b>
fastcall
thiscall
class A { public: int function1(int a,int b); int function2(int a,...); }; int A::function1 (int a,int b) { return a+b; } #include
//函数function1调用 0401C1D push 2 00401C1F push 1 00401C21 lea ecx,[ebp-8] 00401C24 call function1 注意Q这里this没有被入?//函数function2调用 00401C29 push 3 00401C2B push 2 00401C2D push 1 00401C2F push 3 00401C31 lea eax,[ebp-8] q里引入this指针 00401C34 push eax 00401C35 call function2 00401C3A add esp,14h
naked call
__declspec(naked) int add(int a,int b) { __asm mov eax,a __asm add eax,b __asm ret }
mov eax,[ebp+8] add eax,[ebp+12] ret 8
__declspec(naked) int __stdcall function(int a,int b) { __asm mov eax,a __asm add eax,b __asm ret 8 //注意后面? }
函数调用U定D的常见问?/h2>
__declspec(dllexport) int func(int a,int b);//注意Q这里没有stdcallQ用的是cdecl
typedef int (*WINAPI DLLFUNC)func(int a,int b); hLib = LoadLibrary(...); DLLFUNC func = (DLLFUNC)GetProcAddress(...)//q里修改了调用约?result = func(1,2);//D错误
使用Visual C++~程Q有如下Ҏq行文g操作Q?/font>
Q?Q用标准Cq行库函敎ͼ包括fopen、fclose、fseek{?/font>
Q?Q用Win16下的文g和目录操作函敎ͼ如lopen、lclose、lseek{。不q,在Win32下,q些函数主要是ؓ了和Win16向后兼容?/font>
Q?Q用Win32下的文g和目录操作函敎ͼ如CreateFileQCopyFileQDeleteFileQFindNextFileQ等{?/font>
Win32下,打开和创建文仉由CreateFile完成Q成功的话,得到一个Win32下的句柄Q这不同于“C”的fopenq回的句柄。在Win16下,该句柄和Cq行库文件操作函数相宏V但在Win32下,“C”的文g操作函数不能使用该句柄,如果需要的话,可以使用函数_open_osfhandle从Win32句柄得到一个“C”文件函数可以用的文g句柄?/font> 关闭文g使用Win32的CloseHandle?/font> 在Win32下,CreateFile可以操作的对象除了磁盘文件外Q还包括讑֤文g如通讯端口、管道、控制台输入、邮件槽{等?/font>
Q?Q用CFile和其zc进行文件操作。CFile从CObjectzQ其zcd括操作文本文件的CStdioFileQ操作内存文件的CmemFileQ等{。CFile是徏立在Win32的文件操作体pȝ基础上,它封装了部分Win32文g操作函数。最好是使用CFilec(或派生类Q的对象来操作文Ӟ必要的话Q可以从q些cL生自q文g操作cR统一使用CFile的界面可以得到好的移植性?br />
2?a name="_Toc452640992">文g操作的方?
MFC用一些类来封装文件访问的Win32 API。以CFile为基Q从CFilez出几个类Q如CStdioFileQCMemFileQMFC内部使用的CMiororFileQ等{?br />
2.1、CFile的结?/b>
2.1.1、CFile定义的枚丄?/b>
CFilecd义了一些和文g操作相关的枚丄型,主要有四U:OpenFlagsQAttributeQSeekPositionQhFileNull。下面,分别解释q些枚Dcd?/p>
OpenFlags定义?3U文件访问和׃n模式Q?/p>
enum OpenFlags {
//W一Q从叻I下同Q至W二位,打开文g时访问模式,??d
modeRead = 0x0000,
modeWrite = 0x0001,
modeReadWrite = 0x0002,
shareCompat = 0x0000, //32位MFC中没?/p>
//W五到第七位Q打开文g时的׃n模式
shareExclusive = 0x0010,//独占方式Q禁止其他进E读?/p>
shareDenyWrite = 0x0020,//止其他q程?/p>
shareDenyRead = 0x0030,//止其他q程?/p>
shareDenyNone = 0x0040,//允许其他q程?/p>
//W八位,打开文g时的文gl承方式
modeNoInherit = 0x0080,//不允许子q程l承
//W十三、十四位Q是否创建新文g和创建方?/p>
modeCreate = 0x1000,//创徏新文Ӟ文g长度0
modeNoTruncate = 0x2000,//创徏新文件时如文件已存在则打开
//W十五、十六位Q文件以二进制或者文本方式打开Q在zcCStdioFile中用
typeText = 0x4000,
typeBinary = (int)0x8000
};
Attribute定义了文件属性:正常、只诅R隐含、系l文Ӟ文g或者目录等?/p>
enum Attribute {
normal = 0x00,
readOnly = 0x01,
hidden = 0x02,
system = 0x04,
volume = 0x08,
directory = 0x10,
archive = 0x20
}
SeekPosition定义了三U文件位|:头、尾、当前:
enum SeekPosition{
begin = 0x0,
current = 0x1,
end = 0x2
};
hFileNull定义了空文g句柄
enum { hFileNull = -1 };
2.1.2、CFile的其他一些成员变?/b>
CFile除了定义枚DcdQ还定义了一些成员变量。例如:
UINT m_hFile
该成员变量是public讉K属性,保存::CreateFileq回的操作系l的文g句柄。MFC重蝲了运符号HFILE来返回m_hFileQ这样在使用HFILEcd变量的地方可以用CFile对象?/p>
BOOL m_bCloseOnDelete;
CString m_strFileName;
q两个成员变量是protected讉K属性。m_bCloseOnDelete用来指示是否在关闭文件时删除CFile对象Qm_strFileName用来保存文g名?br />
2.1.3、CFile的成员函?/b>
CFile的成员函数实C对Win32文g操作函数的封装,完成以下动作Q打开、创建、关闭文Ӟ文g指针定位Q文件的锁定与解锁,文g状态的d和修改,{等。其中,用到了m_hFile文g句柄的一般是虚拟函数Q和此无关的一般是静态成员函数。一般地Q成员函数被映射到对应的Win32函数Q如?1-1所C?/p>
?1-1 CFile函数对Win32文g函数的封?/p>
虚拟 |
静? |
成员函数 |
对应的Win32函数 |
文g的创建、打开、关? |
|||
? |
Abort |
CloseHandle |
|
? |
Duplicate |
DuplicateHandle |
|
? |
Open |
CreateFile |
|
? |
Close |
CloseHandle |
|
文g的读? |
|||
? |
Read |
ReadFile |
|
ReadHugeQ向后兼容) |
调用Read成员函数 |
||
? |
Write |
WriteFile |
|
WriteHuage(向后兼容) |
调用Write成员函数 |
||
? |
Flush |
FlushFileBuffers |
|
文g定位 |
|||
? |
Seek |
SetFilePointer |
|
SeekToBegin |
调用Seek成员函数 |
||
SeekToEnd |
调用Seek成员函数 |
||
? |
GetLength |
调用Seek成员函数 |
|
? |
SetLength |
SetEndOfFile |
|
文g的锁?解锁 |
|||
? |
LockRange |
LockFile |
|
? |
UnlockRange |
UnlockFile |
|
文g状态操作函? |
|||
? |
GetPosition |
SetFilePointer |
|
GetStatus(CFileStatus&) |
GetFileTime,GetFileSize{? |
||
? |
GetStatus(LPSTR lpszFileName CFileStatus&) |
FindFirstFile |
|
? |
GetFileName |
不是单地映射到某个函? |
|
? |
GetFileTitle |
||
? |
GetFilePath |
||
? |
SetFilePath |
||
? |
SetStatus |
||
改名和删? |
|||
? |
Rename |
MoveFile |
|
? |
Remove |
DeleteFile |
2.1.4、CFile的部分实?/b>
q里主要讨论CFile对象的构造函数和文g的打开/创徏的过E?/p>
CFile有如下几个构造函敎ͼ
~省构造函敎ͼ仅仅构造一个CFile对象Q还必须使用Open成员函数来打开文g?/p>
已经打开了一个文件hFileQ在此基上构造一个CFile对象来给它打包。HFile被赋值给CFile的成员变量m_hFile?/p>
指定一个文件名和文件打开方式Q构造CFile对象Q调用Open打开/创徏文gQ把文g句柄保存到m_hFile?/p>
Open的原型如下:
BOOL CFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags,
CFileException* pException)
Open调用Win32函数::CreateFile打开文gQƈ把文件句柄保存到成员变量m_hFile中?/p>
CreateFile函数的原型如下:
HANDLE CreateFile(
LPCTSTR lpFileName,// pointer to name of the file
DWORD dwDesiredAccess,// access (read-write) mode
DWORD dwShareMode,// share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //pointer to security descriptor
DWORD dwCreationDistribution,// how to create
DWORD dwFlagsAndAttributes,// file attributes
HANDLE hTemplateFile// handle to file with attributes to copy
);
昄QOpen必须把自q两个参数lpszFileName和nOpenFlags映射到CreateFile的七个参C?/p>
从OpenFlags的定义可以看出,QnOpenFlags & 3)表示了读写标识,映射成变量dwAccessQ可以取gؓWin32的GENERIC_READ、GENERIC_WRITE、GENERIC_READ|GENERIC_WRITE?/p>
QnOpenFlags & 0x70)表示了共享模式,映射成变量dwShareModeQ可以取gؓWin32的FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_WRITE|FILE_SHARE_READ?/p>
Open定义了一个局部的SECURITY_ATTRIBUTES变量saQ?nOpenFlags & 0x80)被赋值给sa.bInheritHandle?/p>
(nOpenFlags & modeCreate)表示了创建方式,映射成变量dwCreateFlagQ可以取gؓWin32的OPEN_ALWAYS、CREATE_ALWAYS、OPEN_EXISTING?/p>
在生成了上述参数之后Q先调用::CreateFileQ?/p>
HANDLE hFile =::CreateFile(lpszFileName,
dwAccess, dwShareMode, &sa,
dwCreateFlag, FILE_ATTRIBUTE_NORMAL, NULL);
然后QhFile被赋值给成员变量m_hFileQm_bCloseOnDelete被设|ؓTRUE?/p>
׃可以看出QCFile打开(创徏)一个文件时大大化了:: CreateFile函数的复杂性,卛_需要指定一个文件名、一个打开文g的参数即可。若该参数指定ؓ0Q则表示以只L式打开一个存在的文gQ独占用,不允许子q程l承?/p>
在CFile对象使用Ӟ如果它是在堆中分配的Q则应该销毁它Q如果在栈中分配的,则CFile对象被自动销毁。销毁时析构函数被调用,析构函数是虚拟函数。若m_bCloseOnDelete为真且m_hFile非空Q则析构函数调用Close关闭文g?/p>
至于其他CFile成员函数的实玎ͼq里不作分析了?/p>
2.1.5、CFile的派生类
q里主要要地介绍CStdioFile和CmemFile及CFileFind?/p>
CStdioFileҎ本文件进行操作?/p>
CStdioFile定义了新的成员变量m_pStreamQ类型是FILE*。在打开或者创建文件时Q用_open_osfhandle从m_hFile(Win32文g句柄)得到一个“C”的FILEcd的文件指针,然后Q在文g操作中,使用“C”的文g操作函数。例如,L件用_freadQ而不?:ReadFileQ写文g使用了_fwriteQ而不?:WriteFileQ等{。m_hFile是CFile的成员变量?/p>
另外QCStdioFile不支持CFile的Dumplicate、LockRange、UnlockRange操作Q但是实C两个新的操作ReadString和WriteString?/p>
CMemFile把一块内存当作一个文件来操作Q所以,它没有打开文g的操作,而是设计了Attach和Detach用来分配或者释放一块内存。相应地Q它提供了Alloc、Free虚拟函数来操作内存文Ӟ它覆盖了Read、Write来读写内存文件?/p>
Z方便文g查找QMFC把有兛_能归l成Z个类CFileFind。CFileFindz于CObjectcR首先,它用FindFile和FineNextFile包装了Win32函数::FindFirstFile?:FindNextFileQ其ơ,它提供了许多函数用来获取文g的状态或者属性?/p>
使用CFileStatusl构来描q文件的属性,其定义如下:
struct CFileStatus
{
CTime m_ctime; // 文g创徏旉
CTime m_mtime; // 文g最q一ơ修Ҏ?/p>
CTime m_atime; // 文g最q一ơ访问时?/p>
LONG m_size; // 文g大小
BYTE m_attribute; // 文g属?/p>
BYTE _m_padding; // 没有实际含义Q用来增加一个字?/p>
TCHAR m_szFullName[_MAX_PATH]; //l对路径
#ifdef _DEBUG
//实现Dump虚拟函数Q输出文件属?/p>
void Dump(CDumpContext& dc) const;
#endif
};
例如Q?/p>
CFileStatus status;
pFile->GetStatus(status);
#ifdef _DEBUG
status.dump(afxDump);
#endif
MFCWin32或者C语言的内存分配APIQ有四种内存分配API可供使用?
每一个进E都可以使用堆分配函数创Z个私有的堆──调用q程地址I间的一个或者多个页面。DLL创徏的私有堆必定在调用DLL的进E的地址I间内,只能被调用进E访问?
HeapCreate用来创徏堆;HeapAlloc用来从堆中分配一定数量的I间QHeapAlloc分配的内存是不能Ud的;HeapSize可以定从堆中分配的I间的大;HeapFree用来释放从堆中分配的I间QHeapDestroy销毁创建的堆?
׃Win32采用q面内存l构模式QWin32下的全局和局部内存函数除了名字不同外Q其他完全相同。Q一函数都可以用来分配Q意大的内存Q仅仅受可用物理内存的限Ӟ。用法可以和Win16下基本一栗?
Win32下保留这cdC证了和Win16的兼宏V?
C语言的标准内存分配函数包括以下函敎ͼ
mallocQcallocQreallocQfreeQ等?
q些函数最后都映射成堆API函数Q所以,malloc分配的内存是不能Ud的。这些函数的调式版本?
malloc_dbgQcalloc_dbgQrealloc_dbgQfree_dbgQ等?
虚拟内存API是其他API的基。虚拟内存API以页为最分配单位,X86上页长度?KBQ可以用GetSystemInfo函数提取长度。虚拟内存分配函数包括以下函敎ͼ
DWORD cbSize,
DWORD fdwAllocationType,
DWORD fdwProtect);
该函数用来分配一定范围的虚拟c参?指定起始地址Q参?指定分配内存的长度;参数3指定分配方式Q取值MEM_COMMINT或者MEM_RESERVEQ参?指定控制讉K本次分配的内存的标识Q取gؓPAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS?
LPVOID lpvAddress,
DWORD cbSize,
DWORD fdwAllocationType,
DWORD fdwProtect);
该函数功能类gVirtualAllocQ但是允许指定进Eprocess。VirtaulFree、VirtualProtect、VirtualQuery都有对应的扩展函数?
DWORD dwSize,
DWORD dwFreeType);
该函数用来回收或者释攑ֈ配的虚拟内存。参?指定希望回收或者释攑ֆ存的基地址Q如果是回收Q参?可以指向虚拟地址范围内的M地方Q如果是释放Q参?必须是VirtualAllocq回的地址Q参?指定是否释放或者回收内存,取gؓMEM_DECOMMINT或者MEM_RELEASE?
DWORD cbSize,
DWORD fdwNewProtect,
PDWORD pfdwOldProtect);
该函数用来把已经分配的页改变成保护页。参?指定分配늚基地址Q参?指定保护늚长度Q参?指定늚保护属性,取值PAGE_READ、PAGE_WRITE、PAGE_READWRITE{等Q参?用来q回原来的保护属性?
PMEMORY_BASIC_INFORMATION lpBuffer,
DWORD dwLength
);
该函数用来查询内存中指定늚Ҏ。参?指向希望查询的虚拟地址Q参?是指向内存基本信息结构的指针Q参?指定查询的长度?
该函数用来锁定内存,锁定的内存页不能交换到页文g。参?指定要锁定内存的起始地址Q参?指定锁定的长度?
参数1指定要解锁的内存的v始地址Q参?指定要解锁的内存的长度?br />
C++的new ?delete操作W?
MFC定义了两U作用范围的new和delete操作W。对于newQ不论哪U,参数1cd必须是size_tQ且q回voidcd指针?
原型如下Q?
void _cdecl ::operator new(size_t nSize);
void __cdecl operator delete(void* p);
调试版本Q?
void* __cdecl operator new(size_t nSize, int nType,
LPCSTR lpszFileName, int nLine)
原型如下Q?
void* PASCAL classname::operator new(size_t nSize);
void PASCAL classname::operator delete(void* p);
cȝoperator new操作W是cȝ静态成员函敎ͼ对该cȝ对象来说覆盖全局的operator new。全局的operator new用来l内部类型对象(如intQ、没有定义operator new操作W的cȝ对象分配内存?
new操作W被映射成malloc或者malloc_dbgQdelete被映成free或者free_dbg?
MFC应用E序可以使用Cq行库的调试手段Q也可以使用MFC提供的调试手Dc两U调试手D分别论q如下?br />
Cq行库提供和支持的调试功能如下:
用来报告应用E序的调试版本运行时的警告和出错信息。包括:
_CrtDbgReport 用来报告调试信息Q?/p>
_CrtSetReportMode 讄是否警告、出错或者断a信息Q?/p>
_CrtSetReportFile 讄是否把调试信息写入到一个文件?/p>
断言宏主要有Q?/p>
assert 验某个条件是否满I不满终止程序执行?/p>
验证函数主要有:
_CrtIsValidHeapPointer 验证某个指针是否在本地堆中;
_CrtIsValidPointer 验证指定范围的内存是否可以读写;
_CrtIsMemoryBlock 验证某个内存块是否在本地堆中?/p>
malloc_dbg 分配内存时保存有兛_存分配的信息Q如在什么文件、哪一行分配的内存{。有一pd用来提供内存诊断的函敎ͼ
_CrtMemCheckpoint 保存内存快照在一个_CrtMemStatel构中;
_CrtMemDifference 比较两个_CrtMemStateQ?/p>
_CrtMemDumpStatistics 转储输出一_CrtMemStatel构的内容;
_CrtMemDumpAllObjectsSince 输出上次快照或程序开始执行以来在堆中分配的所有对象的信息Q?/p>
_CrtDumpMemoryLeaks 程序执行以来的内存漏洞Q如果有漏洞则输出所有分配的对象?/p>
2. MFC提供的调试手D?/b>
MFC在Cq行库提供和支持的调试功能基上,设计了一些类、函数等来协助调试?/p>
ASSERT
使用ASSERT断言判定E序是否可以l箋执行?/p>
TRACE
使用TRACE宏显C或者打印调试信息。TRACE是通过函数AfxTrace实现的。由于AfxTrace函数使用了cdecl调用U定Q故可以接受个数不定的参敎ͼ如同printf函数一栗它的定义和实现如下Q?/p>
void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)
{
#ifdef _DEBUG // all AfxTrace output is controlled by afxTraceEnabled
if (!afxTraceEnabled)
return;
#endif
//处理个数不定的参?/p>
va_list args;
va_start(args, lpszFormat);
int nBuf;
TCHAR szBuffer[512];
nBuf = _vstprintf(szBuffer, lpszFormat, args);
ASSERT(nBuf < _countof(szBuffer));
if ((afxTraceFlags & traceMultiApp) && (AfxGetApp() != NULL))
afxDump << AfxGetApp()->m_pszExeName << ": ";
afxDump << szBuffer;
va_end(args);
}
#endif //_DEBUG
在程序源码中Q可以控制是否显Ct信息,昄什么跟t信息。如果全局变量afxTraceEnabled为TRUEQ则TRACE宏可以输出;否则Q没有TRACE信息被输出。如果通过afxTraceFlags指定了跟t什么消息,则输出有兌t信息,例如Z指定“Multilple Application Debug”,令AfxTraceFlags|=traceMultiApp。可以跟t的信息有:
enum AfxTraceFlags
{
traceMultiApp = 1, // multi-app debugging
traceAppMsg = 2, // main message pump trace (includes DDE)
traceWinMsg = 4, // Windows message tracing
traceCmdRouting = 8, // Windows command routing trace
//(set 4+8 for control notifications)
traceOle = 16, // special OLE callback trace
traceDatabase = 32, // special database trace
traceInternet = 64 // special Internet client trace
};
q样Q应用程序可以在需要的地方指定afxTraceEnabled的值打开或者关闭TRACE开养I指定AfxTraceFlags的D滤跟t信息?/p>
Visual C++提供了一个TRACE工具Q也可以用来完成上述功能?/p>
Z昄消息信息QMFC内部定义了一个AFX_MAP_MESSAGcd的数lallMessagesQ储存了Windows消息和消息名映射寏V例如:
allMessages[1].nMsg = WM_CREATE,
allMessages[1].lpszMsg = “WM_CREATE?/p>
MFC内部q用函数_AfxTraceMsg昄跟踪消息Q它可以接收一个字W串和一个MSG指针Q然后,把该字符串和MSG的各个域的信息组合成一个大的字W串q用AfxTrace昄出来?/p>
allMessages和函数_AfxTraceMsg的详l实现可以参见AfxTrace.cpp?/p>
对象内容转储是CObjectcL供的功能Q所有从它派生的c都可以通过覆盖虚拟函数DUMP来支持该功能。在讲述CObjectcL曾提到过?/p>
虚拟函数Dump的定义:
class ClassName : public CObject
{
public:
#ifdef _DEBUG
virtual void Dump( CDumpContext& dc ) const;
#endif
?/p>
};
在用DumpӞ必须l它提供一个CDumpContextcd的参敎ͼ该参数指定的对象负责输试信息。ؓ此,MFC提供了一个预定义的全局CDumpContext对象afxDumpQ它把调试信息输送给调试器的调试H口。从前面AfxTrace的实现可以知道,MFC使用了afxDump输出跟踪信息到调试窗口?/p>
CDumpContextcL有基c,它提供了以文本Ş式输断信息的功能?/p>
例如Q?/p>
CPerson* pMyPerson = new CPerson;
// set some fields of the CPerson object...
//...
// now dump the contents
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif
对象有效性检是CObjectcL供的功能Q所有从它派生的c都可以通过覆盖虚拟函数AssertValid来支持该功能。在讲述CObjectcL曾提到过?/p>
虚拟函数AssertValid的定义:
class ClassName : public CObject
{
public:
#ifdef _DEBUG
virtual void AssertValid( ) const;
#endif
?
};
使用ASSERT_VALID宏判断一个对象是否有效,该对象的cd覆盖了AssertValid函数。Ş式ؓQASSERT_VALID(pObject)?/p>
另外QMFC提供了一些函数来判断地址是否有效Q如Q?/p>
AfxIsMemoryBlockQAfxIsStringQAfxIsValidAddress?/p>
10.1.3内存诊断
MFC使用DEBUG_NEW来跟t内存分配时的执行的源码文g和行数?/p>
?define new DEBUG_NEW插入到每一个源文g中,q样Q调试版本就使用_malloc_dbg来分配内存。MFC Appwizard在创建框架文件时已经作了q样的处理?/p>
MFC提供了函数AfxDoForAllObjects来追t动态分配的内存对象Q函数原型如下:
void AfxDoForAllObjects( void (*pfn)(CObject* pObject,
void* pContext), void* pContext );
其中Q?/p>
参数1是一个函数指针,AfxDoForAllObjectsҎ个对象调用该指针表示的函数?/p>
参数2传递给参数1指定的函数?/p>
AfxDoForAllObjects可以到所有用new分配的CObject对象或者CObjectcL生的对象Q但全局对象、嵌入对象和栈中分配的对象除外?/p>
10.1.4内存漏洞?/strong>
仅仅用于new的DEBUG版本分配的内存?/p>
完成内存漏洞,需要如下系列步骤:
afxMemDFQdelayFreeMemDFQcheckAlwaysMemDF
其中QallocMemDF表示可以q行内存诊断输出QdelayFreeMemDF表示是否是在应用E序l束时才调用free或者deleteQ这样导致程序最大可能的分配内存QcheckAlwaysMemDF表示每一ơ分配或者释攑ֆ存之后都调用函数AfxCheckMemoryq行内存(AfxCheckMemory查堆中所有通过new分配的内存(不含mallocQ)?/p>
q一步是可选步骤,非必R?/p>
例如Q?/p>
// Declare the variables needed
#ifdef _DEBUG
CMemoryState oldMemState, newMemState, diffMemState;
oldMemState.Checkpoint();
#endif
// do your memory allocations and deallocations...
CString s = "This is a frame variable";
// the next object is a heap object
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
#ifdef _DEBUG
newMemState.Checkpoint();
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpStatistics();
//or diffMemState.DumpAllObjectsSince();
}
#endif
MFC在应用程序(调试版)l束Ӟ自动q行内存漏洞,如果存在漏洞Q则输出漏洞的有关信息?/p>