8

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

それで、今日私は誰かに使用するとバグを隠す可能性があることを示したかったprintfのですが、この奇妙なバグ(機能?うーん)を持っていた古いコードを見つけることができません。

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

編集:

私の質問の部分は、私の意見を「使用printfが間違っている」という方向に向けていることがわかります。私はそれを正確に言っているわけではなく、極端な意見を述べるのは好きではないので、少し質問を編集しています。これは優れたツールであることに同意しますが、セグメンテーション違反が解消printfされるケースを再現したかったprintfので、注意が必要であることを証明しました。

4

11 に答える 11

19

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

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

于 2010-06-24T14:13:55.447 に答える
7

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

于 2010-06-24T14:18:01.753 に答える
4

特異なバグを扱っているようですね。

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

于 2010-06-24T14:17:45.420 に答える
2

IMHOすべての開発者は今でもあちこちでプリントアウトに依存しています。それらを「詳細ログ」と呼ぶことを学びました。

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

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

これは素晴らしいことですが、zは実際にはメソッドに含まれていましたが、他のオブジェクトは含まれていませんでした。また、objの式から例外が発生しないようにする必要があります。

プリントアウトが行うもう1つのことは、遅延が発生することです。プリントアウトが導入されると、競合状態のコードが「修正される」ことがあります。いくつかのコードがそれを使用していても驚かないでしょう。

于 2010-06-24T14:14:33.663 に答える
2

Macintosh(1991年頃)でプログラムをデバッグしようとしたときに、32ビットではなく16ビットのアドレス加算を使用したためにコンパイラが生成した32K〜64Kのスタックフレームのクリーンアップコードが誤っていたのを覚えています(16 -アドレスレジスタに追加されるビット量は、68000で符号拡張されます)。シーケンスは次のようなものでした。

  スタックポインタをレジスタにコピーします
  他のレジスタをスタックにプッシュする
  スタックポインタから約40960を引く
  保存されたスタックポインタレジスタだけを残すいくつかのことを行う
  スタックポインタに-8192(0xA000の符号付き解釈)を追加します
  ポップレジスタ
  他のレジスタからスタックポインタをリロードする

正味の効果は、以下を除いてすべてが順調だったということでした保存されたレジスタが破損していて、そのうちの1つが定数(グローバル配列のアドレス)を保持していること。コンパイラーがコードのセクションで変数をレジスターに最適化すると、デバッガーが正しく出力できるように、デバッグ情報ファイルでそのことを報告します。定数がそのように最適化されている場合、コンパイラはそのような情報を含まないようです。必要がないはずだからです。配列のアドレスの「printf」を実行して追跡し、printfの前後のアドレスを表示できるようにブレークポイントを設定しました。デバッガーはprintfの前後のアドレスを正しく報告しましたが、printfが間違った値を出力したため、コードを分解して、printfがレジスタA3をスタックにプッシュしていることを確認しました。

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

于 2010-06-24T15:38:30.310 に答える
1

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

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

関数の本体のどこかにprintfがない限り、関数は確実にセグメンテーション違反をスローすることがわかりました。その場合、意図したとおりに機能します。セグメンテーション違反の修正は、ステップ2でファイルの長さに1を加えた長さを割り当てることでした。

于 2011-03-27T06:21:36.410 に答える
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) );
}

問題はループ条件にあります-ヌル文字「\0」の代わりに「\n」を使用しました。さて、printfがどのように機能するかは正確にはわかりませんが、この経験から、変数の後のメモリ位置を一時/作業スペースとして使用していると推測しています。printfステートメントの結果、単語が格納された後のある場所に'\ n'文字が書き込まれる場合、FixCap関数はある時点で停止できます。printfを削除すると、ループを続け、'\ n'を探しますが、segfaultが発生するまで見つかりません。

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

于 2012-02-20T18:13:11.667 に答える
0

デバッグの場合はどうなりますか?トークン化された方法を確認するchar *[]ために呼び出す前に配列をexec()出力する-これは、のかなり有効な使用法だと思いますprintf()

ただし、提供される形式printf()が十分なコストと複雑さを備えているため、実際にプログラムの実行(ほとんどの場合速度)が変わる可能性がある場合は、デバッガーの方が適している可能性があります。繰り返しになりますが、デバッガーとプロファイラーにもコストがかかります。どちらも、彼らがいないと浮上しないかもしれない種族を暴露するかもしれません。

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

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

于 2010-06-24T14:42:27.207 に答える
0

さて、多分あなたは彼にgdbや他のデバッグプログラムの使い方を教えることができますか?「printf」のおかげでバグが消えたとしても、実際には消えず、後で再び現れる可能性があることを彼に伝えてください。バグは無視するのではなく、修正する必要があります。

于 2010-06-24T14:14:41.197 に答える
0

これにより、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();
}
于 2010-06-24T14:23:22.027 に答える
0

これに対処する1つの方法は、マクロのシステムをセットアップすることです。これにより、コードでprintfsを削除しなくても、printfsを簡単にオフにできます。私は次のようなものを使用します:

#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()実行できることです。関数を変更して、いくつかの異なることを実行できます(printfからstderr、ログファイル、syslogまたはその他のシステムロギング機能の使用など)。 。)、および特定のレベル未満のメッセージは、logging_messagef()不要な場合は無視できます。 PV_DBGMESSAGE()実稼働環境で確実にオフにしたい大量のデバッグメッセージ用です。

于 2010-06-24T15:02:34.990 に答える