10

私はデバッグ能力の向上にかなりの時間を費やすことに興味があり、一般的に使用される高度なデバッグ/テスト手法の原則に精通するためにカバーする必要のあるコアトピックのリストを探しています。

最初は、gdbのドキュメントを読み、その機能からデバッグ手法を収集するだけだと思いました。ただし、セグメンテーションフォールトの行番号を取得するためにそれに飛び込んで実行する以外は、数btか月後もprintfデフォルトの戦略としてマスに頼っています。これは、より洗練された方法で実行できる明確な戦略がないためだと思います。

私の質問はC/C ++に関連しており、UNIX環境内で操作していますが、重要な概念の理解が向上する場合は、一般化された資料や他の言語でカバーされているトピックも検討したいと思います。

4

6 に答える 6

10

考慮すべき複数の直接戦略があります。

  • 大量のprintfsはロギングソリューションを求めています。ここには多くのオプションがありますが、広範囲にログを記録することは特に悪い戦略ではありません。実際、あらゆる形式のクライアント側のデバッグに不可欠です。
  • アサーションを広範に使用します(「リリース」コードであっても、アサーションを無効にしないでください)。常にすべての潜在的なエラーのチェックを記述し、できるだけ早く失敗します(C ++で例外を使用します-常にスローし、キャッチしません)。
  • emacs内でgdbをマスターすることを学ぶことは役に立ちます。プログラムをステップ実行する方法、ブレークポイントを設定する方法、およびローカル変数を検査する方法を学ぶだけで、通常は十分すぎるほどです。
  • ユニットテストも検討する必要があります。特に、小さなテストは、完全な機能を備えたプログラムのノイズに囲まれていないため、デバッグが容易です。コードの前にテストを作成するか、他の誰かにテストを作成してもらいます。

より一般的には、次の点はデバッグに直接関係していませんが、次の点に役立ちます。

  • プログラムがどのように実行されるかを学ぶこと(例えば、スタックフレームとアセンブリの簡単な紹介について学ぶこと)は、バグがメモリを破壊している特定の状況で役立つかもしれません。より一般的には、環境についての学習をやめないでください。
  • C ++では、グッドプラクティスを使用します:RAII、標準ライブラリ、できるだけ早く失敗するなど。これは、特にデバッグの労力を減らす傾向が強いです。デバッガーは標準ライブラリからのものをかなり印刷できるからです。また、可能な限り単純にコーディングすると、デバッグ時間にプラスの効果があります。
  • (分散)バージョン管理を使用します。いつも。慣れると、メリットがわかります(たとえば、単体テストと組み合わせると、全能のユーザーがgit bisect利用できるようになります)。
于 2012-09-01T17:08:30.390 に答える
4

より高度なC/C ++デバッガーになりたい場合は、アセンブリ(専門家である必要はなく、基本的な知識があれば十分です)を学び、マシンレジスタについて学び、プラットフォームのABI(アプリケーションバイナリインターフェイス)について学びます。特に、関数の引数とスタックがどのように機能するか) printf、プログラムが何をしているかを確認するためにどこにでも依存する必要がないようにします。また、メモリを調べる方法を学び、メモリアドレスで何を探しているのかを知ることもお勧めします。アセンブリが適切になり、マシン命令がレジスタセットとどのように相互作用するかを理解すると、どこにあるかがすぐにわかります。ウォッチポイントとして設定するポインタアドレスまたはメモリ位置を検索するか、ブロックとして検索して、特定のメモリ位置のデータ構造で何が起こっているかを確認します。

たとえば、数レベル下で何かが発生している場合、再帰的アルゴリズムのデバッグは困難になる可能性があります...レベルの数によっては、ふるいにかけるのに永遠にかかるデータの連なりを出力してしまう可能性があります。永遠にブレークポイント。ただし、スタックが再帰的アルゴリズムでどのように機能するかを理解している場合は、スタックポインタレジスタを監視する条件付きブレークポイント、およびスタック上の他のメモリアドレスを設定して、再帰的アルゴリズムのエラーが発生した時点でプログラムを適切に停止できます。意味のないデータをふるいにかけるのではなく、発生します。したがって、プログラムが停止するまで実行し、バックトレースを調べ、スタック上のいくつかの変数とスタックポインタレジスタを確認します。

ところで、簡単なメモとして、使用しないでくださいprintf...バッファリングされstdoutます。つまり、出力でエラーが表示されるまでに、バグはすでに奇妙なメモリ破損エラーなどの他の何かに伝播している可能性があります。stdout終了行の文字などを配置してバッファをフラッシュした場合でも、プログラムの通常の出力でエラーメッセージを多重化することになります。printfを使用する代わりにfprintf、に出力しstderrます。これはバッファリングされず、すぐに出力に出力されます。プログラムの出力を保存したい場合は、エラーメッセージがプログラムの出力と混同されることはありません。

于 2012-09-01T17:07:37.910 に答える
3

アサーションを使用する手法についてもう少し説明したいと思います。

アサーションを使用すると、コード内の任意の時点で真であると予想される内容を指定できます。関数の開始時に、アサーションを使用して、パラメーターに適切な値があることを確認できます。これらは前提条件と呼ばれます。そして、関数の終わりに、返そうとしているものが関数の目的と一致していることを確認することができます。これらは事後条件と呼ばれます。関数の途中で、中間計算が妥当であることを確認できます(ただし、通常、関数を十分に小さくして、中間計算が多くないようにする必要があります)。

クラスを使用すると、適切な値がコンストラクターに渡されていることを確認できます。他のメソッドでは、メソッドが戻る前に、クラスの一般的な状態が適切であることを確認できます。これらは不変量と呼ばれます。

私がデバッグしているとき、私は通常、いくつかのアサーションを見逃し、クラッシュを問題の原因から遠ざけるため、バグを見つけるのが難しいことに気付きます。私はそれを修正するためにデバッグプロセスを使用します。クラッシュが実際に発生した場所から始めて、「このレベルの抽象化では、何が問題だったのか」と考えます。関数の途中でクラッシュした場合は、関数に渡されたパラメーターが正しくないことに気付く可能性があるため、関数の先頭近くにアサーションを追加して、それらをキャッチします。次回実行してクラッシュすると、クラッシュはもう少し早く発生します。関数の最上位でクラッシュが発生した場合は、スタックの1レベル上に移動して、「呼び出し元が正しい値を渡さなかったのはなぜですか?」と尋ねます。そうすると、中間計算が間違っていたことに気付くかもしれません。そこで、アサーションを追加して、それを早期にキャッチします。中間計算は、別の関数が間違った値を返したことが原因である可能性があります。その場合は、事後条件アサーションを追加して、それを先にキャッチします。現在の関数に適切なパラメーターが渡されていないことが原因である可能性があるため、この関数に前提条件アサーションを追加します。

アサーションを追加するたびに、問題の実際の原因の近くでクラッシュが発生します。最終的には、実際のロジックエラーでクラッシュが発生するようになり、修正は明らかです。しかし、このプロセスを経ることで、将来の問題を見つけやすくなる可能性も高くなりました。

単体テストを行うときに、同様の推論を適用できます。「この問題を早期に発見できなかった私のテストの何が問題だったのか」と尋ねます。

于 2012-09-01T17:35:03.897 に答える
2

に頼ることは何も悪いことではありませんprintf。実際、これには多くの利点があります。

  • printfコメントアウトされたステートメントは、過去のデバッグの試みを明確に示しており、そのコード領域で何か怪しいことが起こっている可能性があることを示しています。

  • printfステートメントが冗長で巧妙に作成されていれば、ステートメントは簡単に理解できます。初心者でもその意味や使い方を理解しています。

  • printfステートメントは実際にあなたに問題とそれを引き起こす原因について考えることを強制します問題を理解するには、論理制御フローとデータフローに従う必要があります。これにより、システム全体の理解が深まります。

実際、コードのデバッグをツールに依存するように教えられている人は、コードの不正な動作の理由を実際に理解しようとする前に、まずツールを調べる傾向があると思います。もちろん、多くの場合デバッガーが好まれますが、デバッガーに盲目的に目を向けるのではなく、最初に考える人が本質的に問題をよりよく理解し、プログラムの障害状態をよりよく理解し、最終的にはよりよく理解する人であることは明らかです問題の原因

ここでロブ・パイクを引用させてください:

「何かがうまくいかなかったとき、私は反射的に問題を掘り下げ、スタックトレースを調べ、印刷ステートメントを貼り付け、デバッガーを呼び出すなどしました。しかし、ケンは私と私たちのコードを無視して、ただ立って考えました。 d書いたばかりです。しばらくすると、パターンに気づきました。ケンは私が理解する前に問題を理解することが多く、突然「何が悪いのか知っています」と発表しました。彼は通常正しいです。ケンがメンタルモデルを構築していることに気づきました。コードと何かが壊れたとき、それはモデルのエラーでした。その問題がどのように発生するかを考えることによって、彼はモデルが間違っている場所、またはコードがモデルを満たしてはならない場所を直感的に理解しました。

ケンは、デバッグの前に考えることが非常に重要であることを教えてくれました。バグに飛び込むと、コード内のローカルの問題を修正する傾向がありますが、最初にバグについて考え、バグがどのように発生したかを考えると、コード内のより高いレベルの問題を見つけて修正することがよくあります。設計し、さらなるバグを防ぎます。」

于 2012-09-01T21:15:19.717 に答える
1

「テスト駆動開発」(TDD)に関する本、たとえばKentBeckの本を読むことをお勧めします。

于 2012-09-01T17:03:40.643 に答える
1

Valgrindの使用方法を学びましょう。少なくとも、デフォルトのツールであるMemcheckです。次のようなさまざまなメモリ管理の問題をデバッグするときに、多くの時間を節約できます。

  • オーバーランニングおよびアンダーランニングヒープブロック
  • 未定義の値を使用する
  • 割り当てが解除されたオブジェクトの使用
于 2012-09-02T06:40:59.003 に答える