スタック巻き戻しとは何ですか?


193

スタック巻き戻しとは何ですか?調べましたが、啓発的な答えは見つかりませんでした!


76
彼がそれが何であるかを知らない場合、それらがCとC ++で同じでないことを彼にどのように期待することができますか?
dreamlax

@dreamlax:では、「スタック巻き戻し」の概念はCとC ++ではどのように違うのですか?
デストラクタ

2
@PravasiMeet:Cには例外処理がないため、スタックの巻き戻しは非常に単純ですが、C ++では、例外がスローされたり、関数が終了したりした場合、スタックの巻き戻しには、自動ストレージ期間でC ++オブジェクトが破棄されます。
dreamlax

回答:


150

スタックの巻き戻しは通常、例外処理に関連して説明されています。次に例を示します。

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 ++でメモリ、データベース接続、オープンファイル記述子などのリソースを管理するのに役立ちます。

これにより、例外的な安全保証を提供できます。


それは本当に啓発的でした!だから私はこれを得ます:スタックがポップされたときにブロックを離れている間に私のプロセスが予期せずクラッシュした場合、例外ハンドラコードの後のコードがまったく実行されず、メモリリークが発生する可能性があります、ヒープの破損など
Rajendra Uppal 2010

15
プログラムが「クラッシュ」する(つまり、エラーが原因で終了する)場合、メモリは終了時に解放されるため、メモリリークやヒープの破損は関係ありません。
タイラー・マクヘンリー

1
丁度。ありがとう。今日は少しディスレクシアになっています。
Nikolai Fetissov

11
@TylerMcHenry:標準は、リソースまたはメモリが終了時に解放されることを保証しません。しかし、ほとんどのOSはたまたまそうしています。
Mooing Duck

3
delete [] pleak;X == 0の場合にのみ到達する
ジブ

71

これはすべてC ++に関連しています。

定義:オブジェクトを静的に(ヒープメモリに割り当てるのではなくスタック上に)作成し、関数呼び出しを実行すると、それらは「スタック」されます。

スコープ({およびで区切られたもの})が終了すると(を使用しreturn XXX;たり、スコープの最後に到達したり、例外をスローしたりして)、そのスコープ内のすべてが破棄されます(すべてに対してデストラクタが呼び出されます)。ローカルオブジェクトを破棄してデストラクタを呼び出すこのプロセスは、スタックの巻き戻しと呼ばれます。

スタックの巻き戻しに関連する次の問題があります。

  1. RAIIを参照-メモリリーク(ローカルオブジェクトによって管理され、デストラクタでクリーンアップされていない動的に割り当てられたものが漏洩されます)を回避呼ばニコライによって、およびブーストのドキュメント:: scoped_ptrをまたは使用して、この例をのブーストを:: mutexを:: scoped_lock

  2. プログラムの一貫性:C ++仕様では、既存の例外が処理されるまで例外をスローしてはならないことが規定されています。これは、スタックの巻き戻しプロセスで例外がスローされないことを意味します(デストラクタがスローされないことが保証されているコードのみを使用するか、デストラクタのすべてをtry {and で囲みます} catch(...) {})。

スタックの巻き戻し中にデストラクタが例外をスローすると、プログラムが予期せず終了する(最も一般的な動作)、またはユニバースが終了する(理論的には可能ですが、実際にはまだ観察されていない)未定義の動作が発生します。


2
それどころか。gotosは悪用されるべきではありませんが、MSVCでスタックの巻き戻しを引き起こします(GCCではないため、おそらく拡張機能です)。setjmpとlongjmpは、クロスプラットフォームの方法でこれを行いますが、柔軟性はやや劣ります。
Patrick Niedzielski、2010

10
これをgccでテストしたところ、コードブロックの外に移動したときに、デストラクタが正しく呼び出されます。stackoverflow.com/questions/334780/…を参照してください-そのリンクで言及されているように、これも標準の一部です。
Damyan

1
ニコライ、ジュリスタ、そしてあなたの答えをこの順番で読むと、理にかなっています!
n611x007 2012

@sashoalm 7年後に投稿を編集する必要があると本当に思いますか?
David Hoelzer

41

一般的な意味では、スタックの「巻き戻し」は、関数呼び出しの終了とそれに続くスタックのポップとほぼ同じです。

ただし、特にC ++の場合、スタックの巻き戻しは、コードブロックの開始以降に割り当てられたオブジェクトのデストラクタをC ++が呼び出す方法に関係しています。ブロック内で作成されたオブジェクトは、割り当てと逆の順序で割り当て解除されます。


4
tryブロックについて特別なことは何もありません。任意のブロックに割り当てられたスタックオブジェクトはブロックが存在するtry場合に(ブロックに関係なく)巻き戻されます。
Chris Jester-Young

私がC ++コーディングをたくさん行ってからしばらく経ちました。さびた深みからその答えを掘らなければなりませんでした。; P
jrista 2010

心配しないでください。誰もが時々「彼らの悪い」を持っています。
bitc

13

スタックの巻き戻しは、ほとんどの場合C ++の概念であり、スコープが終了したときに(通常または例外によって)スタックに割り当てられたオブジェクトがどのように破棄されるかを扱います。

次のコードの断片があるとします。

void hw() {
    string hello("Hello, ");
    string world("world!\n");
    cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"

これはどのブロックにも当てはまりますか?のみが存在する場合、私は意味{//いくつかのローカルオブジェクト}
ラジェンドラUppal

@Rajendra:はい、匿名ブロックはスコープの領域を定義するため、数えられます。
マイケルマイヤーズ

12

これを読んだかどうかはまだわかりませんが、コールスタックに関するWikipediaの記事にはきちんとした説明があります。

巻き戻し:

呼び出された関数から戻ると、スタックから一番上のフレームがポップされ、おそらく戻り値が残ります。スタックから1つ以上のフレームをポップしてプログラムの他の場所で実行を再開するというより一般的な動作は、スタックの巻き戻しと呼ばれ、例外処理に使用されるものなどの非ローカル制御構造が使用されるときに実行する必要があります。この場合、関数のスタックフレームには、例外ハンドラを指定する1つ以上のエントリが含まれます。例外がスローされると、スローされた例外のタイプを処理(キャッチ)する準備ができているハンドラーが見つかるまで、スタックがほどかれます。

一部の言語には、一般的な巻き戻しを必要とする他の制御構造があります。Pascalを使用すると、グローバルgotoステートメントで、ネストされた関数から、以前に呼び出された外部関数に制御を移すことができます。この操作では、スタックをほどく必要があり、適切なコンテキストを復元して制御を外側の関数内のターゲットステートメントに転送するために必要な数のスタックフレームを削除します。同様に、Cには非ローカルのgotoとして機能するsetjmp関数とlongjmp関数があります。Common Lispでは、unwind-protect特殊演算子を使用して、スタックがほどかれたときに何が起こるかを制御できます。

継続を適用する場合、スタックは(論理的に)解かれ、継続のスタックと共に巻き戻されます。これは継続を実装する唯一の方法ではありません。たとえば、複数の明示的なスタックを使用する場合、継続を適用すると、そのスタックがアクティブになり、渡される値を巻き上げることができます。Schemeプログラミング言語では、継続が呼び出されたときに、コントロールスタックの「巻き戻し」または「巻き戻し」の指定されたポイントで任意のサンクを実行できます。

検査[編集]


9

理解に役立つブログ投稿を読みました。

スタック巻き戻しとは何ですか?

再帰関数(つまり、Fortran 77とBrainf * ckを除くほとんどすべて)をサポートする言語では、言語ランタイムは現在実行中の関数のスタックを保持します。スタックの巻き戻しは、そのスタックを検査し、場合によっては変更する方法です。

なぜそれをしたいのですか?

答えは明白に思えるかもしれませんが、巻き戻しが有用または必要ないくつかの関連する、しかし微妙に異なる状況があります。

  1. ランタイム制御フローメカニズム(C ++例外、C longjmp()など)として。
  2. デバッガーで、ユーザーにスタックを表示します。
  3. プロファイラーで、スタックのサンプルを取得します。
  4. プログラム自体から(スタックを表示するためのクラッシュハンドラーからなど)。

これらには微妙に異なる要件があります。これらのいくつかはパフォーマンスクリティカルであり、いくつかはそうではありません。外枠からレジスタを再構築する機能が必要なものもあれば、そうでないものもあります。しかし、それについてはすぐに説明します。

あなたはここで完全なポストを見つけることができます


7

誰もがC ++での例外処理について話しました。しかし、スタックの巻き戻しには別の意味合いがあり、デバッグに関連していると思います。デバッガーは、現在のフレームの前のフレームに移動することになっている場合は常に、スタックの巻き戻しを行う必要があります。ただし、現在のフレームに戻ったときに巻き戻す必要があるため、これは一種の仮想巻き戻しです。この例は、gdbのup / down / btコマンドです。


5
デバッガーのアクションは通常、単にスタックを解析する「スタックウォーキング」と呼ばれます。「スタック巻き戻し」は、「スタックウォーキング」だけでなく、スタックに存在するオブジェクトのデストラクタを呼び出すことも意味します。
Adisak 2013

@Adisak「スタックウォーキング」とも呼ばれることを知りませんでした。私は常に、すべてのデバッガーの記事のコンテキストで、さらにはgdbコードの中でさえ、「スタックの巻き戻し」を見てきました。「スタックの巻き戻し」は、すべての関数のスタック情報を調べるだけでなく、フレーム情報の巻き戻しを伴うため、より適切であると感じました(dwarfのCFIを参照)。これは、1つの関数を1つずつ順番に処理します。
bbv 2014

「スタックウォーキング」はWindowsでもっと有名になったと思います。また、dwarf標準のドキュメント自体とは別に、code.google.com / p / google-breakpad / wiki / StackWalkingの例として、巻き戻しという用語を数回使用しています。同意するが、それは事実上ほどくことができる。さらに、質問は、「スタックの巻き戻し」が示唆する可能性のあるあらゆる可能な意味を求めているようです。
bbv 14

7

IMO、この記事の以下の図は、スタックの巻き戻しが次の命令のルートに及ぼす影響を美しく説明しています(キャッチされない例外がスローされると実行されます)。

ここに画像の説明を入力してください

写真で:

  • 1つ目は通常の呼び出しの実行です(例外はスローされません)。
  • 例外がスローされたときの一番下。

2番目のケースでは、例外が発生すると、関数呼び出しスタックで例外ハンドラーが線形検索されます。検索は、例外ハンドラー、つまりmain()囲みtry-catchブロックのある関数で終了しますが、関数呼び出しスタックからその前のすべてのエントリを削除する前ではありません


図はいいですが、説明は少しわかりにくいです。 ... try-catchブロックで
Atul

3

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が破棄されます。

これが幸せなコーディングに役立つことを願っています!


2

例外がスローされ、制御がtryブロックからハンドラに渡されると、C ++ランタイムは、tryブロックの開始以降に構築されたすべての自動オブジェクトのデストラクタを呼び出します。このプロセスは、スタックの巻き戻しと呼ばれます。自動オブジェクトは、その構築と逆の順序で破棄されます。(自動オブジェクトは、autoまたはregisterとして宣言されているか、staticまたはexternとして宣言されていないローカルオブジェクトです。xが宣言されているブロックをプログラムが終了すると、自動オブジェクトxが削除されます。

サブオブジェクトまたは配列要素で構成されるオブジェクトの構築中に例外がスローされた場合、デストラクタは、例外がスローされる前に正常に構築されたサブオブジェクトまたは配列要素に対してのみ呼び出されます。ローカル静的オブジェクトのデストラクタは、オブジェクトが正常に構築された場合にのみ呼び出されます。


この回答をコピーした元の記事へのリンクを提供する必要があります
。IBMKnowledge

0

Javaスタックでは、(ガベージコレクターを使用して)巻き戻しや巻き戻しはそれほど重要ではありません。多くの例外処理に関する論文で、私はこの概念(スタックの巻き戻し)を見ました。特に、それらのライターはCまたはC ++での例外処理を扱っています。try catchブロック私たちは忘れてshouln't:ローカルブロックの後に、すべてのオブジェクトから自由にスタックを

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.