質問

Cプログラミングのコースにいたとき、教師が私が使用することを提案したことを覚えています printf 私がデバッグしようとしていたプログラムの実行を見るために。このプログラムには、現時点では覚えていない原因のセグメンテーション過失がありました。私は彼のアドバイスに従い、セグメンテーションの障害が消えました。幸いなことに、賢いTAが使用する代わりにデバッグするように私に言った printfs。この場合、それは便利なことでした。

だから、今日は使用している人に見せたかった printf 潜在的にバグを隠すことができますが、この奇妙なバグ(機能?うーん)がある古いコードが見つかりません。

質問: この行動にも遭遇した人はいますか?どうすればこのようなものを再現できますか?

編集:

私の質問は私の意見を「使っている」と言います printf 間違っている」。私はそれを正確に言っているわけではなく、極端な意見を聞くのが好きではないので、少し質問をしています。 printf 良いツールですが、私はただケースを再現したかっただけです printfsセグメンテーション障害が消えるため、注意する必要があることを証明してください。

役に立ちましたか?

解決

追加する場合の場合があります printf 通話はコードの動作を変更しますが、デバッグが同じことをする場合もあります。最も顕著な例は、マルチスレッドコードのデバッグです。スレッドの実行を停止すると、プログラムの動作が変更されるため、探しているバグは発生しない可能性があります。

だから使用してください printf ステートメントには正当な理由があります。デバッグするか printf ケースバイケースで決定する必要があります。とにかく2つは排他的ではないことに注意してください - あなた できる 含まれていてもデバッグコード printf 電話:-)

他のヒント

ロギングを使用しないように私を説得するのは非常に難しいでしょう(そして、この状況ではロギングの形式がありました)。デバッグします。明らかにクラッシュをデバッグするために、最初のことはバックトレースを取得して浄化または同様のツールを使用することですが、原因が明らかでない場合は、使用できる最良のツールの1つです。デバッガーを使用すると、詳細に集中できます。ロギングにより大きな画像が表示されます。どちらも便利です。

あなたが扱っているように聞こえます heisenbug.

私は本質的に「間違っている」何かがあるとは思わない printf デバッグツールとして。しかし、はい、他のツールと同様に、それはその欠陥があり、はい、printfステートメントを追加するとheisenbugが作成される場合があります。しかし、私はまた、デバッガーによって導入されたメモリレイアウトの変更の結果としてHeisenbugsを表示しました。その場合、Printfはクラッシュにつながるステップを追跡する際に非常に貴重であることが証明されました。

IMHOすべての開発者は、まだ印刷物にあちこちに依存しています。それらを「詳細なログ」と呼ぶことを学びました。

さらに重要なことに、私が見た主な問題は、人々が無敵のようにprintfsを扱うことです。たとえば、Javaでは次のようなものを見ることは珍しくありません

System.out.println("The value of z is " + z + " while " + obj.someMethod().someOtherMethod());

これは素晴らしいことですが、Zが実際にメソッドに関与していたが、他のオブジェクトはそうではなかったことを除いて、OBJの式から例外を取得しないようにすることがあります。

プリントアウトが行うもう1つのことは、彼らが遅延を導入することです。プリントアウトが導入されたときに、レース条件のコードが「修正される」ことがあります。一部のコードがそれを使用しても驚かないでしょう。

マッキントッシュ(1991年頃)でプログラムをデバッグしようとしたことを覚えています。コンパイラの生成されたクリーンアップコードは、32Kから64Kのスタックフレームのクリーンアップコードが誤りでした。 - アドレスレジスタに追加されるビット数量は、68000で標識拡張されます)。シーケンスは次のようなものでした:

  copy stack pointer to some register
  push some other registers on stack
  subtract about 40960 from stack pointer
  do some stuff which leaves saved stack-pointer register alone
  add -8192 (signed interpretation of 0xA000) to stack pointer
  pop registers
  reload stack pointer from that other register

最終的な効果は、すべてがうまくいったことでした それ外 保存されたレジスタが破損し、そのうちの1人が一定(グローバル配列のアドレス)を保持していたこと。コンパイラがコードのセクション中に変数をレジスタに最適化する場合、デバッグインフォメーションファイルでデバッガーが正しく出力できると報告しています。定数が非常に最適化されている場合、コンパイラは明らかにそのような情報を含めていません。配列のアドレスの「printf」を実行して物事を追跡し、ブレークポイントを設定して、printfの前後にアドレスを表示できるようにしました。デバッガーはprintfの前後に住所を正しく報告しましたが、printfは間違った値を出力したため、コードを分解して、printfがレジスタA3をスタックに押し上げているのを見ました。 Printfの前にレジスタA3を表示すると、配列のアドレスとはかなり異なる値があることが示されました(printfは実際に保持されている値を示しました)。

デバッガーとprintfを一緒に使用できなかった場合、どのようにしてそれを追跡したのかわかりません(または、68000アセンブリコードを理解していなかった場合)。

なんとかこれをすることができました。フラットファイルからデータを読んでいました。私の誤ったアルゴリズムは次のようになりました:

  1. 入力ファイルの長さをバイトで取得します
  2. バッファーとして機能するように、可変長の文字の配列を割り当てる
    • ファイルは小さいので、スタックオーバーフローについては心配していませんが、ゼロの長さの入力ファイルはどうですか?おっと!
  3. 入力ファイルの長さが0の場合、エラーコードを返します

私の関数は、関数の本体のどこかにprintfがある場合を除き、私の関数が確実にSEG断層を投げることを発見しました。その場合、それは私が意図したとおりに機能します。 SEG障害の修正は、ステップ2にファイルの長さと1つを割り当てることでした。

同じような経験がありました。これが私の特定の問題と原因です:

// Makes the first character of a word capital, and the rest small
// (Must be compiled with -std=c99)
void FixCap( char *word )
{
  *word = toupper( *word );
  for( int i=1 ; *(word+i) != '\n' ; ++i )
    *(word+i) = tolower( *(word+i) );
}

問題は、ループ条件にあります-Null文字「 0」の代わりに「 n」を使用しました。今、私はprintfがどのように機能するかを正確に知りませんが、この経験から、変数の後に一時的な /作業スペースとしてメモリの位置を使用すると推測しています。 printfステートメントの結果、「 n」文字が私の単語が保存されてからある場所で書かれている場合、Fixcap関数はある時点で停止できます。 printfを削除すると、「 n」を探してループを続けますが、それがセグフォールトするまでそれを見つけることはありません。

したがって、最終的には、私の問題の根本的な原因は、「 0」を意味するときに「 n」と入力することがあることです。それは私が以前に犯した間違いであり、おそらく私が再び作るものです。しかし今、私はそれを探すことを知っています。

さて、GDBまたは他のデバッグプログラムの使用方法を彼に教えることができますか? 「printf」のおかげでバグがJustを消えた場合、それは本当に消えず、後者に再び現れることができると彼に伝えてください。無視されるのではなく、バグを修正する必要があります。

これにより、printf行を削除するときに0の分割が得られます。

int a=10;
int b=0;
float c = 0.0;

int CalculateB()
{
  b=2;
  return b;
}
float CalculateC()
{
  return a*1.0/b;
}
void Process()
{
  printf("%d", CalculateB()); // without this, b remains 0
  c = CalculateC();
}

デバッグケースは何でしょうか?印刷a char *[] 呼び出す前の配列 exec() それがどのようにトークン化されたかを見るために - 私はそれがかなり有効な用途だと思います printf().

ただし、フォーマットが供給された場合 printf() 実際にプログラムの実行を変更する可能性があるため、十分なコストと複雑さがあります(主に速度)、デバッガーがより良い方法である可能性があります。繰り返しになりますが、デバッガーとプロファイラーも犠牲になります。どちらかが、不在で表面化しない可能性のあるレースを暴露する場合があります。

それはすべて、あなたが書いているものとあなたが追いかけているバグに依存します。利用可能なツールはデバッガーです。 printf() (ロガーをprintfにグループ化する)アサーションとプロファイラー。

ブレードドライバーは他の種類よりも優れていますか?必要なものによって異なります。注意してください、私はアサーションが良いか悪いかを言っているのではありません。それらは単なる別のツールです。

これに対処する1つの方法は、マクロのシステムをセットアップすることです。私はこのようなものを使用します:

#define LOGMESSAGE(LEVEL, ...) logging_messagef(LEVEL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);

/* Generally speaking, user code should only use these macros.  They
 * are pithy. You can use them like a printf:
 *
 *    DBGMESSAGE("%f%% chance of fnords for the next %d days.", fnordProb, days);
 *
 * You don't need to put newlines in them; the logging functions will
 * do that when appropriate.
 */
#define FATALMESSAGE(...) LOGMESSAGE(LOG_FATAL, __VA_ARGS__);
#define EMERGMESSAGE(...) LOGMESSAGE(LOG_EMERG, __VA_ARGS__);
#define ALERTMESSAGE(...) LOGMESSAGE(LOG_ALERT, __VA_ARGS__);
#define CRITMESSAGE(...) LOGMESSAGE(LOG_CRIT, __VA_ARGS__);
#define ERRMESSAGE(...) LOGMESSAGE(LOG_ERR, __VA_ARGS__);
#define WARNMESSAGE(...) LOGMESSAGE(LOG_WARNING, __VA_ARGS__);
#define NOTICEMESSAGE(...) LOGMESSAGE(LOG_NOTICE, __VA_ARGS__);
#define INFOMESSAGE(...) LOGMESSAGE(LOG_INFO, __VA_ARGS__);
#define DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#if defined(PAINFULLY_VERBOSE)
#   define PV_DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#else
#   define PV_DBGMESSAGE(...) ((void)0);
#endif

logging_messagef() 個別の関数です .c ファイル。メッセージの目的に応じて、コードにXmessage(...)マクロを使用します。このセットアップの一番のことは、それが同時にデバッグとログのログに機能することです。 logging_messagef() 関数を変更して、いくつかの異なることを行うことができます(Stderrからログファイルへ、Syslogまたはその他のシステムロギング機能など)、特定のレベル以下のメッセージを無視できます。 logging_messagef() あなたがそれらを必要としないとき。 PV_DBGMESSAGE() これらの豊富なデバッグメッセージのためです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top