回答:
スタックの巻き戻しは通常、例外処理に関連して説明されています。次に例を示します。
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
ここで割り当てられたメモリpleak
は、例外がスローされると失われますが、割り当てられたメモリはデストラクタs
によって適切に解放されますstd::string
。スタックに割り当てられたオブジェクトは、スコープが終了すると「巻き戻され」ます(ここでのスコープは関数のものです)func
です)。これは、自動(スタック)変数のデストラクタへの呼び出しを挿入するコンパイラによって行われます。
これはRAIIと呼ばれる手法につながる非常に強力な概念です。つまり、リソースの取得は初期化です。につながります。これは、C ++でメモリ、データベース接続、オープンファイル記述子などのリソースを管理するのに役立ちます。
これにより、例外的な安全保証を提供できます。
delete [] pleak;
X == 0の場合にのみ到達する
これはすべてC ++に関連しています。
定義:オブジェクトを静的に(ヒープメモリに割り当てるのではなくスタック上に)作成し、関数呼び出しを実行すると、それらは「スタック」されます。
スコープ({
およびで区切られたもの}
)が終了すると(を使用しreturn XXX;
たり、スコープの最後に到達したり、例外をスローしたりして)、そのスコープ内のすべてが破棄されます(すべてに対してデストラクタが呼び出されます)。ローカルオブジェクトを破棄してデストラクタを呼び出すこのプロセスは、スタックの巻き戻しと呼ばれます。
スタックの巻き戻しに関連する次の問題があります。
RAIIを参照-メモリリーク(ローカルオブジェクトによって管理され、デストラクタでクリーンアップされていない動的に割り当てられたものが漏洩されます)を回避呼ばニコライによって、およびブーストのドキュメント:: scoped_ptrをまたは使用して、この例をのブーストを:: mutexを:: scoped_lock。
プログラムの一貫性:C ++仕様では、既存の例外が処理されるまで例外をスローしてはならないことが規定されています。これは、スタックの巻き戻しプロセスで例外がスローされないことを意味します(デストラクタがスローされないことが保証されているコードのみを使用するか、デストラクタのすべてをtry {
and で囲みます} catch(...) {}
)。
スタックの巻き戻し中にデストラクタが例外をスローすると、プログラムが予期せず終了する(最も一般的な動作)、またはユニバースが終了する(理論的には可能ですが、実際にはまだ観察されていない)未定義の動作が発生します。
一般的な意味では、スタックの「巻き戻し」は、関数呼び出しの終了とそれに続くスタックのポップとほぼ同じです。
ただし、特にC ++の場合、スタックの巻き戻しは、コードブロックの開始以降に割り当てられたオブジェクトのデストラクタをC ++が呼び出す方法に関係しています。ブロック内で作成されたオブジェクトは、割り当てと逆の順序で割り当て解除されます。
try
ブロックについて特別なことは何もありません。任意のブロックに割り当てられたスタックオブジェクトは、ブロックが存在するtry
場合に(ブロックに関係なく)巻き戻されます。
スタックの巻き戻しは、ほとんどの場合C ++の概念であり、スコープが終了したときに(通常または例外によって)スタックに割り当てられたオブジェクトがどのように破棄されるかを扱います。
次のコードの断片があるとします。
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
これを読んだかどうかはまだわかりませんが、コールスタックに関するWikipediaの記事にはきちんとした説明があります。
巻き戻し:
呼び出された関数から戻ると、スタックから一番上のフレームがポップされ、おそらく戻り値が残ります。スタックから1つ以上のフレームをポップしてプログラムの他の場所で実行を再開するというより一般的な動作は、スタックの巻き戻しと呼ばれ、例外処理に使用されるものなどの非ローカル制御構造が使用されるときに実行する必要があります。この場合、関数のスタックフレームには、例外ハンドラを指定する1つ以上のエントリが含まれます。例外がスローされると、スローされた例外のタイプを処理(キャッチ)する準備ができているハンドラーが見つかるまで、スタックがほどかれます。
一部の言語には、一般的な巻き戻しを必要とする他の制御構造があります。Pascalを使用すると、グローバルgotoステートメントで、ネストされた関数から、以前に呼び出された外部関数に制御を移すことができます。この操作では、スタックをほどく必要があり、適切なコンテキストを復元して制御を外側の関数内のターゲットステートメントに転送するために必要な数のスタックフレームを削除します。同様に、Cには非ローカルのgotoとして機能するsetjmp関数とlongjmp関数があります。Common Lispでは、unwind-protect特殊演算子を使用して、スタックがほどかれたときに何が起こるかを制御できます。
継続を適用する場合、スタックは(論理的に)解かれ、継続のスタックと共に巻き戻されます。これは継続を実装する唯一の方法ではありません。たとえば、複数の明示的なスタックを使用する場合、継続を適用すると、そのスタックがアクティブになり、渡される値を巻き上げることができます。Schemeプログラミング言語では、継続が呼び出されたときに、コントロールスタックの「巻き戻し」または「巻き戻し」の指定されたポイントで任意のサンクを実行できます。
検査[編集]
理解に役立つブログ投稿を読みました。
スタック巻き戻しとは何ですか?
再帰関数(つまり、Fortran 77とBrainf * ckを除くほとんどすべて)をサポートする言語では、言語ランタイムは現在実行中の関数のスタックを保持します。スタックの巻き戻しは、そのスタックを検査し、場合によっては変更する方法です。
なぜそれをしたいのですか?
答えは明白に思えるかもしれませんが、巻き戻しが有用または必要ないくつかの関連する、しかし微妙に異なる状況があります。
- ランタイム制御フローメカニズム(C ++例外、C longjmp()など)として。
- デバッガーで、ユーザーにスタックを表示します。
- プロファイラーで、スタックのサンプルを取得します。
- プログラム自体から(スタックを表示するためのクラッシュハンドラーからなど)。
これらには微妙に異なる要件があります。これらのいくつかはパフォーマンスクリティカルであり、いくつかはそうではありません。外枠からレジスタを再構築する機能が必要なものもあれば、そうでないものもあります。しかし、それについてはすぐに説明します。
誰もがC ++での例外処理について話しました。しかし、スタックの巻き戻しには別の意味合いがあり、デバッグに関連していると思います。デバッガーは、現在のフレームの前のフレームに移動することになっている場合は常に、スタックの巻き戻しを行う必要があります。ただし、現在のフレームに戻ったときに巻き戻す必要があるため、これは一種の仮想巻き戻しです。この例は、gdbのup / down / btコマンドです。
IMO、この記事の以下の図は、スタックの巻き戻しが次の命令のルートに及ぼす影響を美しく説明しています(キャッチされない例外がスローされると実行されます)。
写真で:
2番目のケースでは、例外が発生すると、関数呼び出しスタックで例外ハンドラーが線形検索されます。検索は、例外ハンドラー、つまりmain()
囲みtry-catch
ブロックのある関数で終了しますが、関数呼び出しスタックからその前のすべてのエントリを削除する前ではありません。
C ++ランタイムは、スローとキャッチの間に作成されたすべての自動変数を破棄します。以下の簡単な例では、f1()スローとmain()キャッチの間で、タイプBとAのオブジェクトがこの順序でスタックに作成されます。f1()がスローすると、BおよびAのデストラクタが呼び出されます。
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
このプログラムの出力は
B's dtor
A's dtor
これは、f1()がスローしたときのプログラムのコールスタックが次のようになるためです。
f1()
f()
main()
したがって、f1()がポップされると、自動変数bが破棄され、f()がポップされると、自動変数aが破棄されます。
これが幸せなコーディングに役立つことを願っています!
例外がスローされ、制御がtryブロックからハンドラに渡されると、C ++ランタイムは、tryブロックの開始以降に構築されたすべての自動オブジェクトのデストラクタを呼び出します。このプロセスは、スタックの巻き戻しと呼ばれます。自動オブジェクトは、その構築と逆の順序で破棄されます。(自動オブジェクトは、autoまたはregisterとして宣言されているか、staticまたはexternとして宣言されていないローカルオブジェクトです。xが宣言されているブロックをプログラムが終了すると、自動オブジェクトxが削除されます。
サブオブジェクトまたは配列要素で構成されるオブジェクトの構築中に例外がスローされた場合、デストラクタは、例外がスローされる前に正常に構築されたサブオブジェクトまたは配列要素に対してのみ呼び出されます。ローカル静的オブジェクトのデストラクタは、オブジェクトが正常に構築された場合にのみ呼び出されます。
Javaスタックでは、(ガベージコレクターを使用して)巻き戻しや巻き戻しはそれほど重要ではありません。多くの例外処理に関する論文で、私はこの概念(スタックの巻き戻し)を見ました。特に、それらのライターはCまたはC ++での例外処理を扱っています。try catch
ブロック私たちは忘れてshouln't:ローカルブロックの後に、すべてのオブジェクトから自由にスタックを。