3

私がC/C ++について理解していないのは、次のとおりです。

はい、誰もがそれを使用して非常に高速な実行可能ファイルを取得するため、最適化をオンにしてコンパイルします。

ただし、デバッグ情報をオンにしてコンパイルする場合は、速度は気にしません。では、そのコンパイルモードにさらに多くの情報を含めてみませんか。たとえば、いくつかのsegfaultが発生する前に検出します。事実上、assert(ptr != NULL)ポインタへのすべてのアクセスの前にを挿入しますptr。なぜコンパイラはそれができないのですか?繰り返しになりますが、デフォルトではオフになっているはずですが、そのような可能性があるはずです。

編集:一部の人々は、私が提案した検出は意味をなさないか、レポートがsegmentation faultまだ行っていないことを何もしないと言いました。しかし、私が念頭に置いているのは、より優雅で有益な中止です。これは、問題のあるコードのファイル名と行番号を、同じように出力しassert()ます。

4

9 に答える 9

8

その場合、プログラムは何をすべきですか?それがユーザーにバグを通知する場合、それはsegfaultが行うことです。

続行してバグを回避することになっている場合、どうすればよいかを知ることができますか?

言うまでもなく、適切に続行する方法を魔法のように知っている場合は、リリースビルドにバグがあります(デバッグビルドは、バグを特定して修正するのに役立つことを目的としており、バグを非表示にすることはできません)。


質問に追加された追加情報に応えて(私はあなたの意図を誤解したと思います):

私が念頭に置いているのは、assert()と同じように、問題のあるコードのファイル名と行番号を出力する、より優雅で有益な中止です。

これはコンパイラーが実行できることです。ご存知のように、コンパイラーは基本的にassert()、ポインターが逆参照された場所に自動的に挿入します。これにより、デバッグビルドのサイズが大幅に増加する可能性がありますが、それでも多くの(またはほとんどの)目的で受け入れられる可能性があります。これは、コンパイラが提供する合理的なオプションだと思います。

コンパイラベンダーが何を言うかわかりません...たぶん、MicrosoftのConnectサイトにVC ++製品のリクエストを投稿して、彼らが何を言っているかを確認してください。

于 2009-02-15T01:17:58.377 に答える
7

あなたの提案にはいくつかの大きな問題があります:

コンパイラにどのような条件を検出させますか?Linux / x86では、アラインされていないアクセスが原因SIGBUSでスタックオーバーフローが発生する可能性がありますSIGSEGVが、どちらの場合も、技術的には、これらの状態を検出して「正常に」失敗するアプリケーションを作成できます。 NULLポインタチェックは検出できますが、最も厄介なバグは、ポインタではなく、無効なポインタアクセスが原因NULLです。

CおよびC++プログラミング言語は十分な柔軟性を提供するため、ランタイムが、指定されたランダムアドレスが任意のタイプの有効なポインターであるかどうかを100%成功して判断することは不可能です。

この状況を検出したときにランタイム環境に何をさせたいですか?(魔法を信じない限り)動作を修正することはできません。実行を継続するか、終了することしかできません。しかし、ちょっと待ってください...それは信号が配信されたときにすでに起こっていることです!プログラムが終了し、コアダンプが生成されます。アプリケーション開発者は、そのコアダンプを使用して、プログラムがクラッシュしたときの状態を判断できます。

あなたが提唱していることは、実際には、デバッガー(gdb)または何らかの形式の仮想化(valgrind)でアプリケーションを実行したいように聞こえます。これはすでに可能ですが、開発者以外の人にはメリットがないため、デフォルトで行うのは意味がありません。

コメントに返信するための更新:

デバッグバージョンのコンパイルプロセスを変更する理由はありません。アプリケーションの「穏やかな」デバッグバージョンが必要な場合は、デバッガー内で実行する必要があります。これを透過的に行うスクリプトで実行可能ファイルをラップするのは非常に簡単です。

于 2009-02-15T01:27:55.937 に答える
1

私はマイケル・バーに同意します。これは実際には何の役にも立たず、何の助けにもなりません。

さらに、これは、ヌルポインターよりもはるかに潜行的で追跡が困難な傾向があるダングリングポインターでは機能しません。

少なくともnullポインターを使用すると、参照を解除する前にそれらが有効であることを確認するのは簡単です。

于 2009-02-15T01:24:03.683 に答える
1

assert(ptr != NULL)ポインターの逆参照が機能しない前に単純なポインターが機能しないもう 1 つの理由があります。すべての無効なポインター (NULL として始まったものも含む) が実際には 0 に等しいわけではありません。

最初に、複数のメンバーを持つ構造体がある場合を考えてみましょう。

struct mystruct {
    int first;
    int second;
    int third;
    int fourth;
};

へのポインタptrmystructあり、 にアクセスしようとするとptr->second、コンパイラは 4 (32 ビット整数を想定) をptr指定してそのメモリ位置にアクセスするコードを生成します。が 0 の場合ptr、アクセスされる実際のメモリ位置は 4 になります。これは依然として無効ですが、単純なアサーションではキャッチされません。(コンパイラは、4 を追加する前に のアドレスをチェックすることが合理的に期待できますptr。その場合、アサーションはそれをキャッチします。)

struct mystruct次に、 の配列があり、任意の要素を別の関数に渡す場合を考えてみましょう。配列の 2 番目の要素にアクセスしようとすると、最初のポインタの 16 バイト先から開始されます。正当なポインター演算をキャッチせずに、コンパイラーがすべての場合に確実に必要なことを行うと合理的に期待できる方法はありません。

本当にやりたいことは、オペレーティング システムとハードウェアを使用して、無効でアラインされていないメモリ アクセスをキャッチし、アプリケーションを強制終了してから、必要なデバッグ情報を取得する方法を見つけ出すことです。最も簡単な方法は、デバッガー内で実行することです。Linux で gcc を使用している場合は、「C++ アプリがクラッシュしたときにスタックトレースを生成する方法」を参照してください。他のコンパイラでも同じことを行う同様の方法があると思います。

于 2009-02-15T20:00:52.817 に答える
1

元の投稿者は、アプリをデバッガーで停止したいと考えています。すべてのスタック変数とスタックにアクセスできるため、プログラムがこの状態にある理由を理解する機会があります。

C/C++ で開発している場合、デバッグ メモリ マネージャーを使用すると時間を大幅に節約できます。バッファ オーバーラン、削除されたメモリへのアクセス、メモリ リークなどは、簡単に見つけて修正できます。市場にはいくつかありますが、2 ~ 3 日かけて独自に作成し、必要な機能の 90% を取得することもできます。それらなしでアプリを作成している場合、必要以上に仕事が難しくなっています。

于 2009-02-15T02:38:47.043 に答える
0

つまり、システムがエラーをスローする前に、エラーをスローする必要があると言っているのです....差し迫ったエラーについて警告しますか?

ポイントは何でしょうか?セグメンテーション違反が発生すると、セグメンテーション違反が発生したことを意味します。最初に「セグメンテーション違反が発生します」という別のメッセージは必要ありません。

ここでポイントを完全に見逃していますか?:p

編集:編集 の意味はわかりますが、実装するのは簡単ではありません。問題は、不正なポインタにアクセスした場合にどうなるかを決定するのは、コンパイラ、言語、またはランタイムではないことです。言語は、これについて公式に約束も保証もしません。代わりに、OS は、これがデバッグ実行可能ファイルであることを知らずに、どの行番号が問題を引き起こしたかなどを知らずに、エラーを発生させます。このエラーが示す唯一のことは、「アドレス X にアクセスしようとしましたが、許可できません。死ぬ」ということです。コンパイラはこれで何をすべきですか?

では、この役立つエラー メッセージを生成するのは誰でしょうか? そしてどうやって?確かに、コンパイラはそれを行うことができますが、エラー処理ですべてのポインターアクセスをラップして、セグメンテーション違反/アクセス違反が発生した場合にそれをキャッチし、代わりにアサートをトリガーします. 問題は、これがとてつもなく遅いことです。「リリースが遅すぎる」だけでなく、「使用するには遅すぎる」。また、呼び出し先のすべてのコードにコンパイラがアクセスできることも前提としています。サードパーティのライブラリで関数を呼び出すとどうなりますか? コンパイラはそのライブラリのコードを生成しないため、エラー処理コードでラップできない内部へのポインター アクセス。

OS、関連するシンボルファイルをロードする意思がある/できると仮定して、デバッグ実行可能ファイルを実行しているかどうかを何らかの形で検出するなど、それを実行できます...行番号を出力できるようにするためです。オーバーエンジニアリングについて話します。これはほとんど OS の仕事ではありません。

そして最後に、これを行うことで何を得ることができますか? 単にデバッガを起動してみませんか? このようなことが発生すると、自動的に中断され、正確な行番号とその他すべてが提供されます。

それは可能ですが、非常に複雑で、コンパイラと OS の両方が関与するため、メリットは非常に小さくなります。デバッガーが既に通知できる情報を通知するポップアップ ボックスが表示されます。そして、その情報を使用して、とにかくデバッガーを起動して、何が問題なのかを調べます。

于 2009-02-15T01:39:53.737 に答える
0

OS の一部として assert(prt != NULL) に相当するものが既にあります。そのため、重要な重要なデータを 0 アドレスに上書きしてシステムをめちゃくちゃにするのではなく、segfault が発生します。

于 2009-02-15T02:30:13.117 に答える
0

良いアイデアですが、特定の場合のみです。それは、関数ポインタを逆参照する前です。その理由は、デバッガーが常に起動するためですが、null 関数ポインターを逆参照した後、スタックがショットされます。したがって、問題のあるコードを見つけるのに問題があります。呼び出し元が呼び出す前にチェックした場合、デバッガーは完全なスタックを提供できます。

より一般的なチェックは、ポインタが実行可能なメモリを指しているかどうかを確認することです。NULL はそうではありませんが、多くのオペレーティング システムは CPU 機能を使用して、特定のメモリ セグメントを実行不能にすることもできます。

于 2009-02-16T09:48:25.400 に答える
0

実行可能ファイルのシンボル ファイルがある場合、クラッシュの場所を行番号にマップすることができます。他の人が述べたように、デバッガーで実行している場合、デバッガーはこれを行います。Visual C++ では、プログラムがクラッシュしたときに、クラッシュしたプロセスにデバッガーを接続して問題の場所を確認できる "ジャスト イン タイム" デバッグも提供されます。

ただし、Visual C++ がインストールされていないマシンでこの機能を使用したい場合は、何らかのコーディングを行うことで実現できます。SetUnhandledExceptionFilterプログラムがクラッシュしたときに呼び出される例外ハンドラを設定できます。ハンドラーでは、例外レコードを調べて、SymGetLineFromAddr64実行中のソース行を判別するために使用できます。「デバッグ ヘルプ」ライブラリには、あらゆる種類の情報を抽出できる多くの機能があります。MSDNの記事とwww.debuginfo.comの記事を参照してください。

于 2009-02-15T19:39:39.543 に答える