2009-07-07 105 views
1

一旦從函數返回控件,變量佔用的空間是否會被釋放?什麼時候變量佔用的空間在C++中被釋放?

我以爲它得到了釋放。

這裏我寫了一個函數,即使從函數CoinDenom返回一個數組的局部引用後,它也能正常工作,使用它來打印計算總和所需的最小硬幣數量的結果。 如果空間得到釋放,它如何能夠打印正確答案?

int* CoinDenom(int CoinVal[],int NumCoins,int Sum) { 
    int min[Sum+1]; 
    int i,j; 
    min[0]=0; 
    for(i=1;i<=Sum;i++) { 
    min[i]=INT_MAX; 
    } 

    for(i=1;i<=Sum;i++) { 

    for(j=0;j< NumCoins;j++) { 

     if(CoinVal[j]<=i && min[i-CoinVal[j]]+1<min[i]) { 
     min[i]=min[i-CoinVal[j]]+1; 
     } 
    } 
    } 
    return min; //returning address of a local array 
} 

int main() { 

    int Coins[50],Num,Sum,*min; 
    cout<<"Enter Sum:"; 
    cin>>Sum; 
    cout<<"Enter Number of coins :"; 
    cin>>Num; 
    cout<<"Enter Values"; 
    for(int i=0;i<Num;i++) { 
    cin>>Coins[i]; 
    } 

    min=CoinDenom(Coins,Num,Sum); 
    cout<<"Min Coins required are:"<< min[Sum]; 
    return 0; 
} 

回答

18

函數返回後,局部變量佔用的內存內容未定義,但實際上它會保持不變,直到主動改變它爲止。

如果更改代碼以在填充內存然後使用它之間執行一些重要工作,則會看到它失敗。

+1

僅供參考 - 通過「失敗」,他意味着代碼仍然有效,但會給您帶來不可預知的結果。這通常比傳統意義上的失敗更糟糕(分段錯誤等) – 2009-07-07 13:38:15

+1

或者,如果在某些操作系統上中斷或您收到信號。它可能是那些非常難以追蹤的間歇性錯誤之一。 – keraba 2009-07-07 13:38:54

+0

或者如果返回導致堆棧縮小到足以使操作系統卸載頁面。然後嘗試訪問該頁面可能會導致seg故障。 – 2009-07-07 15:16:02

6

當函數返回時,空間被「解除分配」 - 但這並不意味着數據在內存中不存在。數據仍然在堆棧上,直到其他函數覆蓋它爲止。這就是爲什麼這些類型的錯誤非常棘手 - 有時它會工作得很好(直到突然它沒有)

2

您需要爲堆返回變量分配內存。

int* CoinDenom(int CoinVal[],int NumCoins,int Sum) { 
    int *min= new int[Sum+1]; 
    int i,j; 
    min[0]=0; 
    for(i=1;i<=Sum;i++) { 
    min[i]=INT_MAX; 
    } 

    for(i=1;i<=Sum;i++) { 

    for(j=0;j< NumCoins;j++) { 

     if(CoinVal[j]<=i && min[i-CoinVal[j]]+1<min[i]) { 
     min[i]=min[i-CoinVal[j]]+1; 
     } 
    } 
    } 
    return min; //returning address of a local array 
} 




    min=CoinDenom(Coins,Num,Sum); 
    cout<<"Min Coins required are:"<< min[Sum]; 
    delete[] min; 
    return 0; 

在你的情況下,你只能看到正確的值,因爲沒有人試圖改變它。一般來說這是不可預測的情況。

+1

不是OP所要求的,但這確實是做到這一點的正確/可靠的方式。 – Noldorin 2009-07-07 12:09:01

2

用於數組的變量在堆棧上分配,堆棧對程序完全可用 - 空間不會被阻塞或以其他方式隱藏。

從某種意義上說,它可以被重新分配,以便其他函數調用可以重複使用,也就是說析構函數被調用以獲取分配給它的變量。整數的析構函數是微不足道的,不會做任何事情。這就是爲什麼你可以訪問它,它可能發生的數據還沒有被覆蓋,你可以閱讀它。

1

答案是語言標準允許的內容和原來工作的內容(在這種情況下)之間有區別,因爲具體的實現如何工作。

該標準說明內存不再使用,所以不能被引用。

實際上,堆棧上的局部變量。在應用程序終止之前,堆棧內存不會被釋放,這意味着您將永遠不會出現寫入堆棧內存的訪問衝突/分段錯誤。但是你仍然違反了C++的規則,它並不總是能工作。編譯器可以隨時覆蓋它。

就你而言,陣列數據根本沒有被其他任何東西覆蓋,所以你的代碼出現工作。調用另一個函數,數據被覆蓋。

7

您已發佈的不是C++代碼 - 下面是用C++違法:

int min[Sum+1]; 

但在一般情況下,你的程序具有不確定的行爲。這意味着任何事情都可能發生 - 甚至可能會發揮作用。

0

這可能會或可能不會工作,行爲是未定義的,這樣做肯定是錯誤的。大多數編譯器也給編譯器警告,例如GCC:

test.cpp:8: warning: address of local variable `min' returned 
2

即陣列是堆棧,在大多數實施方式中,是記憶預先分配連續的塊上。你有一個指向堆棧頂部的堆棧指針,堆棧的增長意味着只需要沿着它移動指針。

函數返回時,堆棧指針被設置回來,但內存仍然存在,如果有指向它的指針,可以訪問它,但這樣做不合法 - 沒有任何東西會阻止你,雖然。數組舊空間中的內存值將在堆棧深度下次運行時位於數組所在的區域。

1

它如何能夠打印正確的答案,如果空間得到釋放??

當內存被釋放時,它仍然存在,但它可能被重用用於別的東西。

在你的例子中,數組已被釋放,但其內存還沒有被重用,所以它的內容還沒有被其他值覆蓋,這就是爲什麼你仍然可以從它的價值中寫道。

它不會被重複使用的事實不能保證;而且在解除分配後甚至可以讀取它的事實也無法保證:所以不要這樣做。

0

記憶就像從未變硬的粘土。分配記憶就像從煲裏取出一些粘土。也許你做了一隻貓和一個芝士漢堡。當你完成後,你可以把你的數字放回鍋中,然後放入鍋中,但不會讓它們失去形狀:如果你或其他人看着鍋,他們會繼續觀察你的貓和芝士漢堡坐在粘土堆的頂部,直到其他人來到並使其成爲別的東西。

內存芯片中的NAND門是粘土,拿着NAND門的地層是泥罐,表示變量值的特定電壓是你的雕塑。這些電壓不會僅僅因爲你的程序將它們從它關心的事物列表中刪除了。

0

您需要了解堆棧。添加此功能,

void f() 
{ 
    int a[5000]; 
    memset(a, 0, sizeof(a)); 
} 

然後在調用CoinDenom()之後但在寫入cout之前立即調用它。你會發現它不再有效。

您的本地變量存儲在堆棧中。 CoinDenom()返回一個指向堆棧的內存地址。非常簡單並且遺漏了很多細節,比如在你調用CoinDenom之前,堆棧指針指向0x1000。一個int *(硬幣)被壓入堆棧。這變成了CoinVal []。然後是一個int,Num成爲NumCoins。然後是另一個int,Sum成爲Sum。這是3個整數,4個字節/整數。然後空間爲局部變量:

int min[Sum+1]; 
int i,j; 

這將是(sum + 3)* 4字節/ int。說Sum = 2,這給了我們另外20個字節的總和,所以堆棧指針增加了32個字節到0x1020。 (main的所有本地都在0x1000以下。)min將指向0x100c。當CoinDenom()返回時,堆棧指針遞減「釋放」該內存,但除非另一個函數被調用以將它自己的本地分配到該內存中,否則沒有什麼會改變存儲在該內存中的內容。

有關如何管理堆棧的更多詳細信息,請參見http://en.wikipedia.org/wiki/Calling_convention

相關問題