記憶體的分配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$?[email protected]@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(@[email protected]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(@[email protected]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
我有這個疑問的原因就是:我以為在出作用域的時候不僅呼叫解構函式,還要回收記憶體,其實只是呼叫解構函式,記憶體在方法返回的時候才回收。