2009-07-07 8 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

FYI - "失敗"すると、コードはまだ動作することを意味しますが、予測できない結果が得られます。通常、従来の意味で失敗する(セグメンテーションフォールトなど)より悪いのはどちらですか? –

+1

または、特定のオペレーティングシステムでは、割り込みがオフになったり、信号が受信されたりします。これは追跡が難しい断続的なバグの1つになる可能性があります。 – keraba

+0

または、リターンによってスタックが縮小され、OSによってページがアンロードされる可能性がある場合。ページにアクセスしようとすると、segフォルトが発生することがあります。 –

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

+2

いいえ、それを行うための正確かつ信頼できる方法は、std :: vectorを使用することです。 –

+0

std :: vectorは、あなたのために割り当てと解放のアンダーカバーを呼び出すでしょう、私は、スタックとヒープ変数の間に違いがあるので、関数呼び出しからローカル変数を返すことができないことを指摘しました。 –

2

配列に使用する変数はスタックに割り当てられ、スタックはプログラムで完全に使用可能です。スペースはブロックされていないか、隠されていません。

これは、他の関数呼び出しや、そこに割り当てられた変数に対してデストラクタが呼び出されるという意味で、後で再利用できるという意味で割り当てが解除されます。整数のデストラクタは簡単で何もしません。だからこそあなたはそれにアクセスすることができ、まだデータが上書きされておらず、あなたがそれを読むことができる。

+0

最初の答えスタックを言及したページをスクロールすると、私は見つけました。 –

1

答えは、言語標準が許すものと、特定の実装がどのように機能するかによって(この場合は)動作するものとの間に違いがあるということです。

標準ではメモリはもはや使用されていないと言われているので、参照しないでください。

実際には、スタック上のローカル変数。スタックメモリは、アプリケーションが終了するまで解放されません。つまり、スタックメモリへの書き込みに対するアクセス違反/セグメンテーション違反は発生しません。しかし、あなたはまだC++のルールに違反していますが、それはいつもうまくいくわけではありません。コンパイラはいつでも上書きすることができます。

あなたのコードでは、配列データはまだ何も上書きされていないので、コードと表示されます。別の関数を呼び出すと、データが上書きされます。

7

あなたが投稿したことはC++コードではありません - 次はC++で違法である:

int min[Sum+1]; 

しかし、一般的には、あなたのプログラムは、未定義の動作を示します。それは何かが起こる可能性があることを意味します - それは動作するように見えます。

+0

*本当に*違法なC++ですか?私は本当にC++を知らないが、確かに有効なC言語のように見える。 – Noldorin

+4

それは有効なC99だ。 Sumが定数の場合は有効なC++ですが、変数です。 –

+0

ああ、素敵な発見。私はそれを見つけていなかった。 – jalf

0

これは機能しないかもしれませんが、動作は未定義です。このようにするのは間違いです。

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

すなわちアレイほとんどの実装では、メモリの事前に割り当てられた連続ブロックでスタック上にある:ほとんどのコンパイラは、例えばGCCには、コンパイラの警告を与えます。スタックの一番上を指すスタックポインタがあります。スタックを増やすということは、単にそれに沿ってポインタを動かすことを意味します。

スタックポインタは戻されましたが、メモリはまだ残っていますが、ポインタがあればそれにアクセスできますが、これは正当ではありません。しかし、。配列の古い領域のメモリ値は、次にスタックの深さが配列がある領域を超えるときに変更されます。

1

スペースが解放された場合、正解をどのように印刷できますか?

メモリが割り当て解除されても、メモリはまだ割り当てられていますが、それは他のもののために再利用される可能性があります。

あなたの例では、配列は割り当てが解除されていますが、そのメモリはまだ再利用されていないため、その内容はまだ他の値で上書きされていません。書きました。

まだ再利用されていないということは保証されません。あなたがそれを解読した後でもそれを読むことさえできますという事実も保証されていません。そうしないでください。

0

メモリは、硬化しない粘土のようなものです。記憶を割り当てることは、粘土鍋から粘土を取り出していくのと同じです。たぶんあなたは猫とチーズバーガーを作るでしょう。あなたが終わったら、あなたの数字をポットに戻すことによって粘土の割り当てを解除しますが、ポットに入れても形が失われるわけではありません:あなたや他の人がポットを見ると、彼らはあなたの猫を観察し続けますチーズバーガーは誰か他の人が来て、彼らを何かにするまで粘土スタックの上に座っている。

メモリチップ内のNANDゲートは粘土であり、NANDゲートを保持する層は粘土ポットであり、変数の値を表す特定の電圧はあなたの彫刻である。これらの電圧は、あなたのプログラムが気にするもののリストから外しただけで変わることはありません。

0

スタックを理解する必要があります。この関数を追加すると、CoinDenom()を呼び出した直後ではなく、coutに書き込む前にこの関数を追加してください。あなたはそれがもう働かないことがわかります。

ローカル変数はスタックに格納されます。 CoinDenom()は、スタックを指すメモリアドレスを返します。非常に単純化され、多くの詳細を残して、スタックポインタがCoinDenomを呼び出す直前に0x1000を指していると言います。 int *(コイン)がスタックにプッシュされます。これはCoinVal []になります。次にint、NumCoinsになるNum。次に別のint、SumとなるSum。 4バイト/ intで3 intsです。次に、ローカル変数のためのスペース:

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

(合計+ 3)* 4バイト/ intになります。 Say Sum = 2とすると、合計で20バイトになるので、スタックポインタは32バイト増加し0x1020になります。 (メインのすべての地方の人々は、スタック上で0x1000以下です。)minは0x100cを指します。CoinDenom()が返ってくると、スタックポインタはそのメモリを "解放"しますが、別の関数が呼び出されてそのメモリに割り当てられていない限り、そのメモリに格納されているものを変更することはありません。

スタックの管理方法の詳細については、http://en.wikipedia.org/wiki/Calling_conventionを参照してください。

関連する問題