1. 程式人生 > >記憶體的分配VS回收&建構函式VS解構函式

記憶體的分配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  

我有這個疑問的原因就是:我以為在出作用域的時候不僅呼叫解構函式,還要回收記憶體,其實只是呼叫解構函式,記憶體在方法返回的時候才回收。