前几天发生了这样一件事,我正在调试一个程序,这个程序用了一大堆乱七八糟的指针来处理一个链表,最终在一个指向链表结点的指针上出了问题。我们预计它应当指向的是一个虚基类的对象。我想到第一个问题是:指针所指的地方真的有一个对象吗?出问题的指针值可以被4整除,并且不是NULL的,所以可以断定它曾经是一个有效的指针。通过使用Visual Studio的内存查看窗口(View->Debug Windows->Memory)我们发现这个指针所指的数据是FE EE FE EE FE EE ...这通常意味着内存是曾经是被分配了的,但现在却处于一种未分配的状态。不知是谁、在什么地方把我的指针所指的内存区域给释放掉了。我想要找出一种方案来查出我的数据到底是怎么会被释放的。
对于delete运算符,你可以看到,它不再释放空间。它用与new同样的方法把返回地址提取出来,写到实际分配空间大小的后面(译者注:就是上面分配的16个字节的第9到第12个字节),在最后四个字节中填上DE AD BE EF(译者注:四个十六进制数,当成单词来看正好是dead beef,用来表示内存已释放真是很形象!),并且把剩余的空间(译者注:就是原本实际应该分配而现在应该要释放掉的空间)都填上一个重复的值。
现在,如果程序由于一个错误的指针而出错,我只需打开内存查看窗口,找到出错的指针所指的地方,再往前找16个字节。这里的值就是调用new运算符的地址,接下来四个字节就是实际分配的内存大小,第三个四个字节是调用delete运算符的地址,最后四个字节应该是DE AD BE EF。接下的实际分配过的内存内容应该是77 77 77 77。
要通过这两个返回地址在源程序中分别找到对应的new和delete,可以这样做:首先把表示地址的四个字节的内容倒序排一下,这样才能得到真正的地址,这里因为在Intel平台上字节序是低位在前的。下一步,在源代码上右击点击,选“Go To Diassembly”。在反汇编的窗口上的左边一栏就是机器代码对应的内存地址。按Ctrl + G或选择Edit->Go To...并输入你找到的地址之一。反汇编的窗口就将滚动到对应的new或delete的函数调用位置。要回到源程序只需再次右键单击,选择“Go To Source”。您就可以看到相应的new或delete的调用了。
[区分三个不同的 new] new 操作符(new 表达式, new operator, new expression): 通常我们调用 X * pX = new X 时使用的就是这个操作符, 它由语言内建, 不能重载, 不能改变其行为. 它包括分配内存的 operator new 和调用构造函数的 placement new. operator new :opeator new 是一个函数, void * operator new(size_t size) . 它分配指定大小的内存, 可以被重载, 可以添加额外的参数, 但第一个参数必须为 size_t. operator new 除了被 new operator 调用外也可以直接被调用: void * rawMem = operator new(sizeof(X)). placement new : placement new 在一块指定的内存上使用构造函数, 包含头文件 之后也可以直接使用 placement new: X * pX = new (rawMem) X. [2]
new_handler curhandler = set_new_handler(0); set_new_handler(curhandler); // get current new handler
if(curhandler == 0) (*curhandler)(); else throw std::bad_alloc(); } } 重载 operator delete 简单许多, 只需注意 C++ Standard 要求删除一个 NULL 是安全的即可.
4.重载 operator new
opeator new 的重载和其它操作符大不相同.首先, 即使你不重载, 默认的 operator new 也可施用于你的自定义型别上(operator, 也具有此特性), 而其它操作符如果不进行重载就无法使用. 其次, 其它重载其它操作符时参数个数都是固定的, 而 operator new 的参数个数是可以任意的, 只需要保证第一个参数为 size_t, 返回类型为 void * 即可, 而且其重载的参数类型也不必包含自定义类型. 更一般的说, operator new 的重载更像是一个函数的重载, 而不是一个操作符的重载.
[★ 用不同的参数重载 operator new]
通过使用不同的参数类型, 可以重载 operator new, 例如 : void * operator new(size_t size, int x, int y, int z) { ... }
X * pX = new (1, 2, 3) X; 你还可以为 operator new 的重载使用默认值, 其原则和普通函数重载一样, 只要不造成和已存在的形式发生冲突即可. 可能你已经想到了, 你甚至还可以在 operator new 中使用不定参数, 如果你真的需要的话. void * operator new(size_t size, int x, int y = 0, int z = 0) { ... }
X * pX = new (10) X; Y * pY = new (10, 10) Y; Z * pZ = new (10, 10, 10) Z;
为某个 class 重载 operator new 时必须定义为类的静态函数[4], 因为 operator new 会在类的对象被构建出来之前调用. 即是说调用 operator new 的时候还不存在 this 指针, 因此重载的 operator new 必须为静态的. 当然在类中重载 operator new 也可以添加额外的参数, 并可以使用默认值.另外, 和普通函数一样, operator new 也是可以继承的. class X{ ... static void * operator new(size_t size); // ... (1) static void * operator new(size_t size, int); // ... (2) };
class Y : public X{ ... };
class Z : public X{ ... static void * operator new(size_t size); // ... (3) };
X * pX1 = new X; // call (1) X * pX2 = ::new X; // call default operator new X * pX3 = new (0) X; // call (2)
Y * pY1 = new Y; // call (1)
Z * pZ1 = new Z; // call (3) Z * pZ2 = ::new Z; // call default operator new Z * pZ3 = X::new Z; // error, no way to call (1) Z * pZ4 = new (0) Z; // error, no way to call (2)
1. Bjarne Stroustrup.The C++ Programming Language(Special Edition) 2. Scott Meyers.Effective C++(2nd Edition) 3. Andrei Alexandrescu.Modern C++ Design 4. Robert B.Murray.C++ Strategies and Tactics 5. Scott Meyers.More Effective C++ 6. John Lakos.Large-Scale C++ Software Design 7. Scott Meyers.Implementing operator->* for Smart Pointers
heapchk 在堆中运行一致性检测。 int heapchk(void); 例程 需要的头文件 可选的头文件 兼容性 heapchk Win NT 对于另外兼容性的信息,参见引言中的兼容性 库 LIBC.LIB 单线程静态库,零售版本 LIBCMT.LIB 多线程静态库,零售版本 MSVCRT.LIB MSVCRT.DLL的输入库,零售版本 返回值 heapchk 返回如下显式常量之一,它们定义在MALLOC.H中 HEAPBADBEGIN 初始头信息是坏的或不能找到。 HEAPBADNODE 坏结点已找到或堆损坏了。 HEAPBADPTR 堆的指针无效。 HEAPEMPTY 堆没有初始化。 HEAPOK 堆是一致的。 另外,如果出现一个错误,heapchk设置errno为ENOSYS。 说明 heapchk 函数通过检测堆的最小一致性来帮助调试堆有关的问题。 例子/* HEAPCHK.C: This program checks the heap for * consistency and prints an appropriate message. #include #include void main( void ) { intheapstaus; char *buffer; /* Allocate and deallocate some memory */ if( (buffer = (char *) malloc( 100 )) != NULL )free( buffer );/* Check heap status */ heapstatus = heapchk(); switch( heapstatus ) { case HEAPOK:printf(" OK - heap is fine\n" ); break; case HEAPEMPTY:printf(" OK - heap is empty\n" ); break; case HEAPBADBEGIN:printf( "ERROR - bad start of heap\n" ); break; case HEAPBADNODE:printf( "ERROR - bad node in heap\n" ); break; } } 输出结果 OK - heap is fine 参见 heapadd,heapmin,heapset,heapwalk
返回顶端
hapmin 释放未用的堆存储器给操作系统。 int hapmin(void); 宏 需要的头文件 可选的头文件 兼容性 hapmin Win NT 对于另外兼容性的信息,参见引言中的兼容性 库 LIBC.LIB 单线程静态库,零售版本 LIBCMT.LIB 多线程静态库,零售版本 MSVCRT.LIB MSVCRT.DLL的输入库,零售版本 返回值如果成功,heapmin返回0;否则该函数返回-1并设置errno为ENOSYS。 说明 heapmin函数通过释放未用的堆存储器给操作系统来最小化堆。 注意:在Visual C++ 4.0版本的低层堆结构移到了C运行库来支持新的调试特征。结果,heapmin 仅支持的Win32平台是Windows NT。当从其它非Win32平台调用时,它立即返回-1并设置errno为ENOSYS。 参见free,heapadd,heapchk,heapset,heapwalk,malloc
返回顶端
heapset 检测堆的最小一致性并设置自由项为一个指定的值。 int heapset(unsigned int fill); 例程 需要的头文件 可选的头文件 兼容性 heapset Win NT 对于另外兼容性的信息,参见引言中的兼容性 库 LIBC.LIB 单线程静态库,零售版本 LIBCMT.LIB 多线程静态库,零售版本 MSVCRT.LIB MSVCRT.DLL的输入库,零售版本 返回值 heapset 返回如下显式常量之一,它们定义在MALLOC.H中 HEAPBADBEGIN 初始头信息是坏的或不能找到。 HEAPBADNODE 堆损坏了或坏结点已找到。 HEAPEMPTY 堆没有初始化。 HEAPOK 堆是一致的。 另外,如果出现一个错误,heapset设置errno为ENOSYS。 说明 heapchk 函数通过检测堆的最小一致性来帮助调试堆有关的问题。 参数 fill 填充字符。 说明 heapset 函数给出自由存储器位置或己经无意覆盖的结点。 heapset检测堆的最小一致性,然后设置该堆的自由项的每个字节为fill值。 这个己知值显示包含自由结点的堆的存储器位置,该结点包含无意写到自由存储器的数据注意:在Visual C++ 4.0版本的低层堆结构移到了C运行库来支持新的调试特征。 结果, heapset 仅支持的Win32平台是Windows NT。 当从其它非Win32平台调用时,它返回HEAPOK并设置errno为ENOSYS 例子 /* HEAPSET.C: This program checks the heap and * fills in free entries with the character 'Z'. */#include #include #include void main( void ) { intheapstatus; char *buffer; if( (buffer = malloc( 1 )) == NULL )/* Make sure heap is*/exit( 0 ); /*initialized */ heapstatus = heapset( 'Z' );/* Fill in free entries */ switch( heapstatus ) { case HEAPOK:printf(" OK - heap is fine\n" ); break; case HEAPEMPTY:printf(" OK - heap is empty\n" ); break; case HEAPBADBEGIN:printf(" ERROR - bad start of heap\n" ); break; case HEAPBADNODE:printf(" ERROR - bad node in heap\n" ); break; } free( buffer ); } 输出结果 OK - heap is fine 参见 heapadd,heapchk,heapmin,heapwalk
返回顶端
heapwalk 遍历堆并返回下一个项的信息。 int heapwalk(HEAPINFO *entryinfo); 例程 需要的头文件 可选的头文件 兼容性 heapwalk Win NT 对于另外兼容性的信息,参见引言中的兼容性 库 LIBC.LIB单线程静态库,零售版本 LIBCMT.LIB 多线程静态库,零售版本 MSVCRT.LIB MSVCRT.DLL的输入库,零售版本 返回值 heapwalk 返回如下整数显式常量,它们定义在MALLOC.H中: HEAPBADBEGIN 初始头信息是坏的或不能找到。 HEAPBADNODE 坏结点已找到或堆损坏了。 HEAPBADPTRHEAPINFO 结构的pentry域不包含堆的有效指针。 HEAPEND 成功到达堆的末尾。 HEAPEMPTY 堆没有初始化。 HEAPOK 迄今还没有错误; HEAPINFO 结构包含下一个项的信息。 另外,如果出现一个错误,heapchk设置errno为ENOSYS。 参数 entryinfo包含堆信息的缓冲区。 说明 heapwalk 函数帮助调试程序中堆有关的问题。该函数通过堆,每次调用遍历一个项并返回类型HEAPINFO结构的指针,该结构包含下一个堆项的信息。 HEAPINFO 类型定义在MALLOC.H中包含如下元素: int*pentry堆项指针。 sizetsize堆项尺寸。 Intuseflag 指出堆项是否正在使用的标志。 调用heapwalk返回HEAPOK时则在size域中存储项的尺寸并设置useflag域为FREEENTRY或USEDENTRY(这两个常量定义在MALLOC.H中)。为了获取堆中第一个项的信息,传送给heapwalk一个其pentry成员为NULL的HEAPINFO结构的指针。注意:从Visual C++ 4.0版本开始,低层堆结构移到了C运行库来支持新的调试特征。 结果, heapwalk 仅支持的Win32平台是Windows NT。 当从其它非Win32平台调用时,它返回HEAPEND并设置errno为ENOSYS。 例子/* HEAPWALK.C: This program "walks" the heap, starting * at the beginning (pentry = NULL). It prints out each * heap entry's use, location, and size. It also prints * out information about the overall state of the heap as * soon as heapwalk returns a value other than HEAPOK. * /#include #include void heapdump( void ); void main( void ); { char *buffer; heapdump(); if( (buffer = amlloc( 59 )) != NULL ) {heapdump(); free( buffer ); } heapdump(); } void heapdump( void ) { HEAPINFO hinfo; int heapstatus; hinfo.pentry = NULL; while( ( heapstatus = heapwalk( &hinfo ) ) == HEAPOK ) { printf( "%6s block at %Fp of size %4.4X\n",( hinfo.useflag == USEDENTRY ? "USED" : "FREE" ),hinfo.pentry, hinfo.size ); } switch( heapstatus ) { case HEAPEMPTY:printf(" OK - empty heap\n" ); break; case HEAPEND:printf(" OK - end of heap\n" ); break; case HEAPBADPTR:printf(" ERROR - bad pointer to heap\n" ); break; case HEAPBADBEGIN:printf(" ERROR - bad start of heap\n" ); break; case HEAPBADNODE:printf(" ERROR - bad node in heap\n" );break; } } 输出 USED block at 002C0004 of size 0014 USED block at 002C001C of size 0054 USED block at 002C0074 of size 0024 USED block at 002C009C of size 0010 USED block at 002C00B0 of size 0018 USED block at 002C00CC of size 000C USED block at 002C00DC of size 001C USED block at 002C00FC of size 0010 USED block at 002C0110 of size 0014 USED block at 002C0128 of size 0010 USED block at 002C013C of size 0028 USED block at 002C0168 of size 0088 USED block at 002C01F4 of size 001C USED block at 002C0214 of size 0014 USED block at 002C022C of size 0010 USED block at 002C0240 of size 0014 USED block at 002C0258 of size 0010 USED block at 002C026C of size 000C USED block at 002C027C of size 0010 USED block at 002C0290 of size 0014 USED block at 002C02A8 of size 0010 USED block at 002C02BC of size 0010 USED block at 002C02D0 of size 1000 FREE block at 002C12D4 of size ED2C OK - end of heap 参见heapadd,heapchk,heapmin,heapset