3
#include <stdio.h>

int doHello(){
    doHello();
}

int main(){
    doHello();
    printf("\nLeaving Main");
    return 0;
}

これを実行すると、画面に「Leaving Main」というメッセージを表示せずにプログラムが終了します。これはスタック オーバーフローのケースで、プログラムが終了しているためにコマンド ウィンドウにエラー メッセージが表示されません。(Windows/Cygwin/で実行)

Q1. doHello 関数でローカル変数を宣言していませんが、まだスタックが使用されています。これのせいか

  • 戻り値
  • 関数呼び出しについて保存された情報?

明確化

Q2. プログラムでそのようなケースをデバッグする方法は? 上で述べた無限ループをデバッグするように求めているわけではありません。

例えば:

#define SIZE 512*1024
void doOVerflow(){
   char str[SIZE];
   doHello();
}

void doHello(){
   char strHello[256];  // stack gets filled up at this point
   doNothing();         // program terminates and function doNothing does not get called
}

編集:

Q3. ランタイム スタックにはどのような情報が格納されますか?

4

6 に答える 6

10

通常は、フレーム ポインタリターン アドレスです。たとえば、ウィキペディアの「コール スタック」の記事を参照してください。興味がある場合:

$ gcc -S test.c  # <--- assembles, but does not compile, test.c; output in test.s
$ cat test.s
// [some contents snipped]
_doHello:
        pushl   %ebp        // <--- pushes address of stack frame onto stack
        movl    %esp, %ebp  // <--- sets up new stack frame
        call    _doHello    // <--- pushes return value onto stack, makes call
        popl    %ebp        // <--- pops address of stack frame off stack
        ret                 // <--- pops return value off stack, returns to it

楽しみのために、「-fomit-frame-pointers」を試してください。

$ gcc -fomit-frame-pointers -S test.c
$ cat test.s
// [some contents snipped]
_doHello:
        call    _doHello   // <--- pushes return value onto stack, makes call
        ret                // <--- pops return value off stack, returns to it

さらに楽しくするために、最適化をオンにするとどうなるか見てみましょう。

$ gcc -fomit-frame-pointers -O4 -S test.c # <--- heavy optimization
$ cat test.s
// [some contents snipped]
_doHello:
L2:
        jmp     L2         // <--- no more stack operations!

最後のバージョンは、少なくとも私のセットアップ (現時点では cygwin) では、終了するのではなく、永久に実行されます。

このような問題を診断するには、お気に入りのデバッガー(Microsoft Visual C++ や gdb など) で実行するか、これらのデバッガーを使用してほとんどのシステムで通常生成されるスタックダンプ (.core または .stackdump ファイル) を調べることができます。

デバッガーがこれをサポートしている場合は、スタックの最上部近くにハードウェア ブレークポイントを設定することもできます。この変数に書き込もうとすると、スタックがいっぱいになる可能性があります。一部のオペレーティング システムには、スタック オーバーフローを警告する追加のメカニズムがあります。

最後に、 valgrindApplication Verifierなどのデバッグ環境が役立つ場合があります。

于 2009-08-15T19:40:19.107 に答える
6

呼び出しが行われたアドレスは、関数を呼び出すたびにスタックに格納されます。

于 2009-08-15T19:21:47.460 に答える
3

ローカル変数がなくても、スタック フレームにはリターン アドレスが含まれます。

注: 最適化レベルが十分に高い最新のコンパイラは、テール コールの最適化を実行できるため、スタック オーバーフローを回避できます。

于 2009-08-15T19:25:04.160 に答える
2

これはaのせいですか?戻り値 b. 関数呼び出しについて保存された情報?

あなたの場合、スタックを消費しているのはリターンアドレスです。

プログラムでそのようなケースをデバッグする方法は?

そもそも書かないでください。再帰関数には常に終了条件が必要です。

Q3. 実行時スタックに格納される情報は?

ほとんどの言語では、関数呼び出しごとのローカル変数と、関数呼び出しごとの戻りアドレス。

関数呼び出しが行われると、現在の実行ポイントがスタックにプッシュされ、関数のコードが実行されます。その実行が終了すると、スタック上の値がポップされ、実行パスはその時点に戻ります。コードでは、戻りアドレスがスタックにプッシュされ、関数が呼び出されます。関数の呼び出しが最初に行うことは、それ自体を呼び出すことです。つまり、戻りアドレスがスタックにプッシュされ、関数が呼び出されます。オン、無限、またはスタック オーバーフローが発生するまで。

于 2009-08-15T19:21:35.073 に答える
1

他の連中は、スタック オーバーフローが発生する理由に既に対処しています。

デバッグに関する追加のメモ。最新のデバッガーのほとんどは、スタック オーバーフローで中断し、デバッガーでコール スタックを確認できます。その時点で、同じ関数が何度も表示されます。この種の問題を診断するために最新のデバッガーを使用していない場合は、不必要に自分を苦しめることになります。

于 2009-08-15T19:37:37.130 に答える
1

関数を分解して分析します。

最初のコードを見てみましょう ( so.cとして保存されます):

int doHello(){
    doHello();
}

int main(){
    doHello();
    printf("\nLeaving Main");
    return 0;
}

doHello() をコンパイルおよび逆アセンブルします。

$ gcc -Wall -ggdb3 -O0 so.c -o so
$ gdb --annotate=3 so
(gdb) disassemble doHello
Dump of assembler code for function doHello:
0x080483e4 <doHello+0>: push   %ebp
0x080483e5 <doHello+1>: mov    %esp,%ebp
0x080483e7 <doHello+3>: sub    $0x8,%esp
0x080483ea <doHello+6>: call   0x80483e4 <doHello>
0x080483ef <doHello+11>:        leave
0x080483f0 <doHello+12>:        ret
End of assembler dump.

これで、関数のアセンブリ コードが得られ、関数が何を行っているかを正確に確認できます。たまたま、この質問に対する答えが私たちを直視しています。最初の命令は dword (リターン ポインター) をスタックにプッシュします。

これが物事を明確にすることを願っています。

乾杯。

于 2009-08-15T20:48:54.087 に答える