顺着看下来一直到004021C9,这句过后可得出ECX里存放的是pItem指向的地址。紧接着一直到004021D0,它是对类析构函数的调用。再向下看程序只做了一些检查就直接调用了释放内存的调用,这也能解释为什么能看到那一句输出。再想想上面一起讨论了new的过程,pItem指向的地址向前4字节才是new所得内存的开始,但看004021E2这句它直接把pItem指向的地址传给了释放内存的调用,这当参数当然不是一个有效的内存块的起始地址了。如果深入跟踪的话可以了解到在debug版本在堆内存的管理中,为每一块申请的内存建立了一个链表项并存放在一个链表中,释放时就去链表里找。而传入的那个地址显然不是一个已申请内存块的正确地址,所以系统会报错。
在上面实验的类中把CMemoryItem析构函数的virtual修饰符去掉,现在再把它加做上面的实验看一下情况会有变化吗?在析构函数中加不加virtual修饰符真的在释放时没有差别吗?那么再看一下单个对象的释放。
4.2 解释类对象时的跟踪
同样先把CMemoryItem类的析构函数的virtual修饰符去掉,然后来看两段代码片段的具体执行情况。
代码1:
CMemoryItem* pItem = new CMemoryItem;
delete pItem;
执行正常,再看下面的代码。
代码2:
CMemoryItem* pItem = new CMemoryItem;
delete[] pItem;
弹出了上面贴出的那个出错框。大家可能觉得这很正常,那来看看一段不正常的。现在再把类析构函数的virtual修饰符加在上面的实验看一下情况。代码1没错(肯定的),再执行代码2也没错。是不是有些奇怪了,为了了解其中原因就看它的汇编代码。下面列出代码:
00401E89 mov edx,dword ptr [ebp-20h]
00401E8C mov eax,dword ptr [edx]
00401E8E mov ecx,dword ptr [ebp-20h]
00401E91 call dword ptr [eax]
EDX 和ECX存放是的pItem指向的地址,因为这个类的析构函数加了virtual修饰符,那么这个类的前四个字节就是析构函数的指针。00401E91这句也就调用了类的析构函数,这自然不会错。那么为什么类的析构函数不加virtual修饰符时就会出错呢?下面也列出一段汇编代码大家便可明白。
00401E87 mov ecx,dword ptr [ebp-20h]
00401E8A call @ILT+170(CMemoryItem::`vector deleting destructor') (004010af)
类的析构函数不加virtual修饰符时, delete[] pItem;系统把它当成一个对象数组去释放自然会出错。这不是说只要为类的析构函数加了virtual修饰符就可以不分数组还是单个对象都用delete[]释放了。因为不同编译器或相同编译器的不同版会有不同的编译方法,只是对VC6.0编译后的程序做了剖析。
5 结语 这里讨论了VC6.0中new和deltte对象的过程,希望能给大家的编程带来一些帮助。
|