4

次の 2 つのコード スニペットで、cout と printf のスタック オーバーフローでアクセス違反が発生する理由を知りたいと思いました。

スタック オーバーフローではなく、最初のコードのアクセス違反の理由を知りたかったのです。

Access Violation を取得する最初のコード:

void Test();

void Test()
{
    static int i = 0;
        cout << i++ << endl;    
    Test();
}
int main() 
{

    Test();
    
    return 0;
}

スタックオーバーフローを取得する2番目のコード:

void Test();

void Test()
{
    static int i = 0;
        printf("%d\n", i++);    
    Test();
}
int main() 
{

    Test();
    
    return 0;
}
4

6 に答える 6

18

無限再帰を試行した後、スタックが枯渇するために両方の関数がクラッシュすることを理解していると思います。あなたが求めているのは、cout の例が「スタック オーバーフロー」でもクラッシュしないのはなぜですか?

答えは、コンパイラによる末尾再帰の検出と関係があるとは思いません。コンパイラが再帰を最適化した場合、どちらの例もクラッシュしません。

私は何が起こっているかについて推測しています。「スタック オーバーフロー」例外は、場合によっては (Windows など)、スタックの最後に割り当てられた単一の仮想メモリ「ガード ページ」で実装されます。スタック アクセスがこのガード ページにヒットすると、特殊な例外タイプが生成されます。

Intel の小粒度ページの長さは 4096 バイトであるため、ガード ページはそのサイズのメモリ範囲をガードします。関数呼び出しが 4096 バイトを超えるローカル変数を割り当てる場合、そこからの最初のスタック アクセスが実際にはガード ページを超えて拡張される可能性があります。次のページは予約されていないメモリであることが予想されるため、その場合はアクセス違反が発生します。

もちろん、例ではローカル変数を明示的に宣言していません。operator<<() メソッドの 1 つが 1 ページ以上のローカル変数を割り当てると仮定します。つまり、operator<<() メソッドまたは cout 実装のその他の部分 (一時オブジェクト コンストラクターなど) の開始付近でアクセス違反が発生するということです。

また、あなたが書いた関数であっても、 operator<<() の実装は、中間結果用のストレージを作成する必要があります。そのストレージは、おそらくコンパイラによってローカル ストレージとして割り当てられます。ただし、あなたの例では合計で 4k になるとは思えません。

本当に理解する唯一の方法は、アクセス違反のスタック トレースを見て、どの命令がそれを引き起こしているかを確認することです。

アクセス違反のスタック トレースと、障害のあるオペコードの領域周辺の逆アセンブリを取得しましたか?

Microsoft C コンパイラを使用している場合は、別の可能性として、printf() と独自の関数が /Ge でコンパイルされ、operator<<() がコンパイルされていないか、関数のみが /Ge でコンパイルされ、記述されているものと同様の要因が含まれている可能性があります。上記の偶然の一致により、表示される動作が発生します。これは、printf() の例では、関数が呼び出されているときにクラッシュが発生し、operator<<() の場合はライブラリを呼び出しているときに発生するためです。

于 2009-10-15T07:27:21.640 に答える
4

両方の再帰関数が停止することはありません。2番目のケースでは、コンパイラによってテールの最適化が行われないため、スタックがオーバーフローするようです。

于 2009-10-15T07:19:44.837 に答える
2

私のマシンでは、両方の関数がスタック オーバーフローを引き起こします。MS Visual Studio 2005 でコンパイルしています。調査に役立つプラットフォームとコンパイラを指定する必要があるかもしれません...

デバッグ モードで何かをコンパイルし、「cout」実装にスタックの破損のために実行できないチェックアップが含まれている可能性がありますか? スタック オーバーフローから回復しようとして、無効な戻りアドレスをポップするコードをコンパイラが生成したのではないでしょうか? 多分あなたはそれをモバイルデバイスで実行していますか?プラットフォームとコンパイラを知らずに言うのは難しいです。

于 2009-10-15T07:36:01.083 に答える
1

無限再帰呼び出しは、スタックオーバーフローに対するものです。アクセス違反に関しては...それは本当にSTLストリームの実装に依存します。あなたは見つけるためにストリームのソースコードを見る必要があります...

于 2009-10-15T07:25:00.600 に答える
0

ほとんどの人があなたの質問を誤解していますが、答えはそこにあります。

2 番目の例は、すべての関数呼び出しがフレームをスタックにプッシュするため、スタック オーバーフローで終了します。最終的には大きくなりすぎます。ソースを見ないと、ストリームの例がアクセス違反で終了する理由を知るのは難しいという Cătălin Pitiş に同意します。

于 2009-10-15T07:31:27.123 に答える
0

これは、スタックが破損し、デバッガーが失敗したプログラムのクラッシュをキャッチしないという問題を覚えています

于 2009-10-20T14:28:06.460 に答える