内存泄露----关于内置对象和类对象的delete与delete【】问题
#include <string>
using namespace std;
int main()
{
int *i_p = new int[5];
delete i_p; //1
// delete []i_p; //2
string *s_p = new string[5];
// delete s_p; //3
delete []s_p; //4
return 0;
}
/**症状:*/
上述代码,在vc6环境,debug运行下,第1与2行的效果是一样的,通过检测,均没有造成内存泄露;而第3与4行的效果则大相径庭。采用第3行,编译连接通过,但执行出错。第4行为正解。
/**试问:*/
对于内建类型,用delete【】和delete效果相同,系统会自动删除所有;而对于类类型,二者不同可能引起内存泄露。请问编译器在
此问题上如何处理的,它做了些什么?
/**附注:*/
上述所谓检测手段,分别采用了5种方法来检测,症状均类似。其中包括MSDN中提到的“#ifdef _DEBUG
CMemoryState oldMem, newMem, diffMem;
oldMem.Checkpoint();
......
newMem.Checkpoint();
diffMem.Difference( oldMem, newMem )
......”
[解决办法]
class A{public: A() { printf("Constr A\n"); } ~A() { printf("Destr A\n"); }};class B{public: B() { printf("Constr B\n"); } ~B() { printf("Destr B\n"); }};class C{public: C() { printf("Constr C\n"); }};int main(int argc, char* argv[]){ A *a=new A[2]; B *b=(B*)a; delete [] b; C *c=new C[2]; a=(A*)c; delete a; c=new C[2]; delete [] c; c=new C[2]; delete c; c=new C[2]; a=(A*)c; delete [] a; a=new A[2]; c=(C*)a; delete [] c; return 0;}
[解决办法]
classTwo
{
inta;
public:
Two( int b = 0){a = b;};
~Two( void ){a= 0;};
};
int main(int argc, char* argv[])
{
char *p = new char;
char *p1= new char[10];
Two *p2 = new Two( 10 );
Two *p3 = new Two[20];
p1[0] = *p;
delete p;
delete p1;
delete p2;
delete p3;
delete []p3;
return 0;
}
汇编代码:
0041EF30 push ebp
0041EF31 mov ebp,esp
0041EF33 push 0FFh
0041EF35 push offset __ehhandler$_main (00426f36)
0041EF3A mov eax,fs:[00000000]
0041EF40 push eax
0041EF41 mov dword ptr fs:[0],esp
0041EF48 sub esp,9Ch
0041EF4E push ebx
0041EF4F push esi
0041EF50 push edi
0041EF51 lea edi,[ebp-0A8h]
0041EF57 mov ecx,27h
0041EF5C mov eax,0CCCCCCCCh
0041EF61 rep stos dword ptr [edi]
16: char *p = new char;
0041EF63 push 1
0041EF65 call operator new (0040ec40)
0041EF6A add esp,4
0041EF6D mov dword ptr [ebp-20h],eax
0041EF70 mov eax,dword ptr [ebp-20h]
0041EF73 mov dword ptr [ebp-10h],eax
17: char *p1= new char[10];
0041EF76 push 0Ah
0041EF78 call operator new (0040ec40) 调用new
0041EF7D add esp,4
0041EF80 mov dword ptr [ebp-24h],eax
0041EF83 mov ecx,dword ptr [ebp-24h]
0041EF86 mov dword ptr [ebp-14h],ecx
18: Two *p2 = new Two( 10 );
0041EF89 push 4
0041EF8B call operator new (0040ec40) 调用new
0041EF90 add esp,4
0041EF93 mov dword ptr [ebp-2Ch],eax
0041EF96 mov dword ptr [ebp-4],0
0041EF9D cmp dword ptr [ebp-2Ch],0
0041EFA1 je main+82h (0041efb2)
0041EFA3 push 0Ah
0041EFA5 mov ecx,dword ptr [ebp-2Ch]
0041EFA8 call @ILT+510(Two::Two) (00401203) 调用构造器
0041EFAD mov dword ptr [ebp-58h],eax
0041EFB0 jmp main+89h (0041efb9)
0041EFB2 mov dword ptr [ebp-58h],0
0041EFB9 mov edx,dword ptr [ebp-58h]
0041EFBC mov dword ptr [ebp-28h],edx
0041EFBF mov dword ptr [ebp-4],0FFFFFFFFh
0041EFC6 mov eax,dword ptr [ebp-28h]
0041EFC9 mov dword ptr [ebp-18h],eax
19: Two *p3 = new Two[20];
0041EFCC push 54h
0041EFCE call operator new (0040ec40)
0041EFD3 add esp,4
0041EFD6 mov dword ptr [ebp-34h],eax
0041EFD9 mov dword ptr [ebp-4],1
0041EFE0 cmp dword ptr [ebp-34h],0
0041EFE4 je main+0E4h (0041f014)
0041EFE6 push offset @ILT+525(Two::~Two) (00401212) 保存了析构地址
0041EFEB push offset @ILT+505(Two::`default constructor closure') (004011fe)
0041EFF0 mov ecx,dword ptr [ebp-34h]
0041EFF3 mov dword ptr [ecx],14h
0041EFF9 push 14h
0041EFFB push 4
0041EFFD mov edx,dword ptr [ebp-34h]
0041F000 add edx,4
0041F003 push edx
0041F004 call `eh vector constructor iterator' (0041f1a0) 调用矢量构造迭代器
0041F009 mov eax,dword ptr [ebp-34h]
0041F00C add eax,4
0041F00F mov dword ptr [ebp-5Ch],eax
0041F012 jmp main+0EBh (0041f01b)
0041F014 mov dword ptr [ebp-5Ch],0
0041F01B mov ecx,dword ptr [ebp-5Ch]
0041F01E mov dword ptr [ebp-30h],ecx
0041F021 mov dword ptr [ebp-4],0FFFFFFFFh
0041F028 mov edx,dword ptr [ebp-30h]
0041F02B mov dword ptr [ebp-1Ch],edx
20:
21: p1[0] = *p;
0041F02E mov eax,dword ptr [ebp-14h]
0041F031 mov ecx,dword ptr [ebp-10h]
0041F034 mov dl,byte ptr [ecx]
0041F036 mov byte ptr [eax],dl
22:
23: delete p;
0041F038 mov eax,dword ptr [ebp-10h]
0041F03B mov dword ptr [ebp-38h],eax
0041F03E mov ecx,dword ptr [ebp-38h]
0041F041 push ecx
0041F042 call operator delete (00409f20) 调用delete
0041F047 add esp,4
24: delete p1;
0041F04A mov edx,dword ptr [ebp-14h]
0041F04D mov dword ptr [ebp-3Ch],edx
0041F050 mov eax,dword ptr [ebp-3Ch]
0041F053 push eax
0041F054 call operator delete (00409f20) 调用delete
0041F059 add esp,4
25: delete p2;
0041F05C mov ecx,dword ptr [ebp-18h]
0041F05F mov dword ptr [ebp-44h],ecx
0041F062 mov edx,dword ptr [ebp-44h]
0041F065 mov dword ptr [ebp-40h],edx
0041F068 cmp dword ptr [ebp-40h],0
0041F06C je main+14Dh (0041f07d)
0041F06E push 1
0041F070 mov ecx,dword ptr [ebp-40h]
0041F073 call @ILT+530(Two::`scalar deleting destructor') (00401217) 调用标量析构器
0041F078 mov dword ptr [ebp-60h],eax
0041F07B jmp main+154h (0041f084)
0041F07D mov dword ptr [ebp-60h],0
26: delete p3;
0041F084 mov eax,dword ptr [ebp-1Ch]
0041F087 mov dword ptr [ebp-4Ch],eax
0041F08A mov ecx,dword ptr [ebp-4Ch]
0041F08D mov dword ptr [ebp-48h],ecx
0041F090 cmp dword ptr [ebp-48h],0
0041F094 je main+175h (0041f0a5)
0041F096 push 1
0041F098 mov ecx,dword ptr [ebp-48h]
0041F09B call @ILT+530(Two::`scalar deleting destructor') (00401217) 调用标量析构器
0041F0A0 mov dword ptr [ebp-64h],eax
0041F0A3 jmp main+17Ch (0041f0ac)
0041F0A5 mov dword ptr [ebp-64h],0
27: delete []p3;
0041F0AC mov edx,dword ptr [ebp-1Ch]
0041F0AF mov dword ptr [ebp-54h],edx
0041F0B2 mov eax,dword ptr [ebp-54h]
0041F0B5 mov dword ptr [ebp-50h],eax
0041F0B8 cmp dword ptr [ebp-50h],0
0041F0BC je main+19Dh (0041f0cd)
0041F0BE push 3
0041F0C0 mov ecx,dword ptr [ebp-50h]
0041F0C3 call @ILT+535(Two::`vector deleting destructor') (0040121c) 调用矢量析构器
0041F0C8 mov dword ptr [ebp-68h],eax
0041F0CB jmp main+1A4h (0041f0d4)
0041F0CD mov dword ptr [ebp-68h],0
28: return 0;
0041F0D4 xor eax,eax
29: }
0041F0D6 mov ecx,dword ptr [ebp-0Ch]
0041F0D9 mov dword ptr fs:[0],ecx
0041F0E0 pop edi
0041F0E1 pop esi
0041F0E2 pop ebx
0041F0E3 add esp,0A8h
0041F0E9 cmp ebp,esp
0041F0EB call __chkesp (0040bfc0)
0041F0F0 mov esp,ebp
0041F0F2 pop ebp
0041F0F3 ret
[解决办法]
现在市面上能找到的书上是这样记载的,
int *a = new int[NUM];
delete a;
只有 第一个元素会被释放。
但是 跟进微软的代码可以看见,
void operator delete(
void *pUserData
)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData); //注意这一句!!!!
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
上面 pHead = pHdr(pUserData); 直接将一个内存地址转换成了_CrtMemBlockHeader*类型,而 这个结构体是这样定义的
typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
#ifdef _WIN64
/* These items are reversed on Win64 to eliminate gaps in the struct
* and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
* maintained in the debug heap.
*/
int nBlockUse;
size_t nDataSize; //注意 这一句!!!
#else /* _WIN64 */
size_t nDataSize;
int nBlockUse;
#endif /* _WIN64 */
long lRequest;
unsigned char gap[nNoMansLandSize];
/* followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
} _CrtMemBlockHeader;
size_t nDataSize; 可以明确的记录你分配内存的大小,我跟过代码。确实很准。
我想这就是为什么内建型不会报错也不会内存泄露的原因。微软在new的时候就已经记录了数据大小。以释放时使用。
而为什么在释放自创型别的时候会报错,我想应该是由于程序简单的调用了free而没有析构过程。
呵呵,先猜想,再看看各位的答案。