之前有一個問題一直困擾着我,就是一個變量出了作用域,我以為這個變量的內存就被回收了,其實不是這樣的,昨天問了一個高手,才豁然開朗,自己在看相關代碼的反匯編代碼,才知道原來真是這樣就。這個問題,我想簡單的說一下內存的分配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
我有這個疑問的原因就是:我以為在出作用域的時候不僅調用析構函數,還要回收內存,其實只是調用析構函數,內存在方法返回的時候才回收。