内存的分配VS回收&构造函数VS析构函数
之前有一个问题一直困扰着我,就是一个变量出了作用域,我以为这个变量的内存就被回收了,其实不是这样的,昨天问了一个高手,才豁然开朗,自己在看相关代码的反汇编代码,才知道原来真是这样就。这个问题,我想简单的说一下内存的分配VS回收&构造函数VS析构函数之间的关系。
我的疑问:为什么p出了作用域,指向p的ptr还能读到p中arr的内容,难道p出了作用域,还没有析构?
下面的内容会解答这个疑问,先说说跟这篇文章有关的内容。
可能是因为平时习惯的原因,我们在实例化一个对象的时候,往往是一条语句实现两个功能:1分配内存;2调用构造函数
class A { public: A() { i=0; j=0; } ~A(){} int i; int j; }; A a1; A * a2=new A();
这两中方式都是一步实现两个操作,分配内存和调用构造函数,如果A没写构造函数,即没有构造函数(编译器也不会自动生成),当然就不需要调用构造函数。
其实这两步是可以分开的,A a1;这句分开不了这两步,但A * a2=new A();是可以分开,同等的代码如下:
void* memory=operator new(sizeof(A));//分配内存
A* a2=new(memory) A();//在memory上调用A的构造函数
回收的时候,我们可以这样写:
delete a2;//这句等同下面两句
//a2->~A();
//operator delete(memory);
如果A没有析构函数,当然delete时也不会调用,原因请看我的博客:构造函数产生的点及原因。
也就是说A* a=new A();delete a;这两条语句,执行了四个操作:
分配内存->调用构造函数->调用析构函数->回收内存;
更多关于这四步分开的代码:
而我今天要说的是,这四步是完全可以分开的。既然这四步是可以分开的,那么解答上面那个疑问就很简单了。
Char* ptr;
{
Point p;
ptr=p;
}
P出了作用域,为什么ptr还能读到他的内容,原因很简单:因为上面几行代码只执行了前面三步,最后一步回收内存,还没有执行。出了作用域,就会执行析构,没说要回收内存,栈的内存要在方法返回之前才回收,也就是说一个方法如果大量的分配内存是很容易爆栈,即是你让栈中的变量出了作用域也没用,请不要搞混了。栈内存在方法返回的时候才回收,这一点就是爆栈的最重要原因,为什么不是在变量出作用域的时候,调用完析构函数,就回收内存呢?我也不知道为什么?,看方法test11的反汇编代码,的确是在方法返回的时候才回收内存?
那个疑问的源码如下:
#include "stdafx.h" #include <iostream> using namespace std; struct Point { char arr[10]; Point() { for(int i=0;i<9;i++) { arr[i]='a'; } arr[9]='\0'; } ~Point(){} operator char*() { return arr; } }; void test11() { char* ptr; { Point p; ptr=p; } cout<<ptr<<endl; } int _tmain(int argc, _TCHAR* argv[]) { { test11(); } system("pause"); return 0; }
test11的反汇编代码如下:
void test11() { 010431F0 push ebp //ebp表示栈顶指针 010431F1 mov ebp,esp //esp表示栈当前指针 009C31F3 push 0FFFFFFFFh 009C31F5 push offset __ehhandler$?test11@@YAXXZ (9CA3C8h) 009C31FA mov eax,dword ptr fs:[00000000h] 009C3200 push eax 009C3201 sub esp,0E4h 009C3207 push ebx 009C3208 push esi 009C3209 push edi 009C320A lea edi,[ebp-0F0h] B::`scalar deleting destructor': 009C3210 mov ecx,39h 009C3215 mov eax,0CCCCCCCCh 009C321A rep stos dword ptr es:[edi] 009C321C mov eax,dword ptr [___security_cookie (9CF070h)] 009C3221 xor eax,ebp 009C3223 mov dword ptr [ebp-10h],eax 009C3226 push eax 009C3227 lea eax,[ebp-0Ch] 009C322A mov dword ptr fs:[00000000h],eax char* ptr; { Point p; 009C3230 lea ecx,[p] 009C3233 call Point::Point (9C1541h) 009C3238 mov dword ptr [ebp-4],0 ptr=p; 009C323F lea ecx,[p] 009C3242 call A::~A (9C1546h) 009C3247 mov dword ptr [ebp-18h],eax } 009C324A mov dword ptr [ebp-4],0FFFFFFFFh 009C3251 lea ecx,[p] 009C3254 call A::`scalar deleting destructor' (9C154Bh) cout<<ptr<<endl; 009C3259 mov esi,esp 009C325B mov eax,dword ptr [__imp_std::endl (9D039Ch)] 009C3260 push eax 009C3261 mov ecx,dword ptr [ebp-18h] 009C3264 push ecx 009C3265 mov edx,dword ptr [__imp_std::cout (9D03A0h)] 009C326B push edx 009C326C call std::operator<<<std::char_traits<char> > (9C132Fh) 009C3271 add esp,8 009C3274 mov ecx,eax 009C3276 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (9D0390h)] 009C327C cmp esi,esp 009C327E call @ILT+960(__RTC_CheckEsp) (9C13C5h) } 009C3283 push edx 009C3284 mov ecx,ebp 009C3286 push eax 009C3287 lea edx,[ (9C32C0h)] 009C328D call @ILT+350(@_RTC_CheckStackVars@8) (9C1163h) 009C3292 pop eax //pop开始出栈 注意;这里才开始回收内存 09C3293 pop edx 009C3294 mov ecx,dword ptr [ebp-0Ch] 009C3297 mov dword ptr fs:[0],ecx 009C329E pop ecx 009C329F pop edi 009C32A0 pop esi 009C32A1 pop ebx 009C32A2 mov ecx,dword ptr [ebp-10h] 009C32A5 xor ecx,ebp 009C32A7 call @ILT+65(@__security_check_cookie@4) (9C1046h) 009C32AC add esp,0F0h 009C32B2 cmp ebp,esp 009C32B4 call @ILT+960(__RTC_CheckEsp) (9C13C5h) 009C32B9 mov esp,ebp //栈顶指针和栈当前指针指向同一个地址,即栈的长度就是一个指针的长度 009C32BB pop ebp //栈顶指针弹出,现在栈空了 009C32BC ret
我有这个疑问的原因就是:我以为在出作用域的时候不仅调用析构函数,还要回收内存,其实只是调用析构函数,内存在方法返回的时候才回收。