組み込みシステムのデバッグにprintf()が悪いのはなぜですか?


16

を使用してマイクロコントローラベースのプロジェクトをデバッグしようとするのは悪いことだと思いprintf()ます。

出力する場所が事前に定義されておらず、貴重なピンを消費する可能性があることを理解できます。同時に、カスタムDEBUG_PRINT()マクロを使用してIDE端末に出力するためにUART TXピンを消費する人もいます。


12
誰が悪いと言ったのですか?「通常は最高ではない」は、資格のない「悪い」と同じではありません。
スペロペファニー14

6
オーバーヘッドがどれだけあるかについてのすべての話です。必要なのは「I'm here」メッセージを出力するだけで、printfはまったく必要なく、単にUARTに文字列を送信するルーチンです。それに加えて、UARTを初期化するコードは、おそらく100バイト未満のコードです。いくつかの16進値を出力する機能を追加しても、それほど増加することはありません。
tcrosley 14

7
@ChetanBhargava-Cヘッダーファイルは通常、実行可能ファイルにコードを追加しません。宣言が含まれています。残りのコードが宣言されたものを使用しない場合、それらのコードはリンクされません。printfもちろん、使用する場合、実装printfに必要なすべてのコードが実行可能ファイルにリンクされます。しかし、それは、ヘッダーのためではなく、コードで使用されているためです。
ピートベッカー14

2
@ChetanBhargava私が説明したような文字列を出力するために独自の単純なルーチンをロールする場合は、<stdio.h>をインクルードする必要さえありません( '\ 0'が表示されるまでUARTに文字を出力します) '
tcrosley

2
@tcrosley優れた最新のコンパイラを使用している場合、おそらくこのアドバイスは無意味だと思います。フォーマット文字列gccを使用せずに単純なケースでprintfを使用し、他のほとんどの場合は、記述どおりのより効率的な単純な呼び出しに置き換えます。
バリティ

回答:


24

printf()を使用することのいくつかの短所を思い付くことができます。「組み込みシステム」は、数百バイトのプログラムメモリを備えたものから、ギガバイトのRAMとテラバイトの不揮発性メモリを備えた本格的なラックマウントQNX RTOS駆動システムにまで及ぶことに留意してください。

  • データを送信する場所が必要です。すでにシステムにデバッグポートまたはプログラミングポートがある場合もあれば、そうでない場合もあります。そうでない場合(または、あなたが持っているものが機能していない場合)、あまり便利ではありません。

  • これは、すべてのコンテキストで軽量な機能ではありません。メモリが数Kしかないマイクロコントローラを使用している場合、これは大きな問題になる可能性があります。printfでのリンクは、それ自体で4Kを消費する可能性があるためです。32Kまたは256Kのマイクロコントローラーを使用している場合、おそらく大きな問題ではありません。大きな組み込みシステムがある場合はもちろんです。

  • メモリの割り当てや割り込みに関連する特定の種類の問題を見つけるのにほとんどまたはまったく役に立ちません。また、ステートメントが含まれているかどうかに応じてプログラムの動作を変更できます。

  • それはタイミングに敏感なものを扱うためにかなり役に立たないです。ロジックアナライザー、オシロスコープ、プロトコルアナライザー、またはシミュレーターを使用した方がよいでしょう。

  • 大きなプログラムがあり、printfステートメントを変更して変更するときに何度も再コンパイルする必要がある場合、多くの時間を無駄にする可能性があります。

良いこと-すべてのCプログラマーがゼロ学習曲線を使用する方法を知っている事前にフォーマットされた方法でデータを出力する簡単な方法です。デバッグしているカルマンフィルターの行列を吐き出す必要がある場合は、MATLABが読み取れる形式で吐き出すのが良いかもしれません。 。

矢筒では役に立たない矢印とは思いませんが、gdbやその他のデバッガー、エミュレーター、ロジックアナライザー、オシロスコープ、静的コード分析ツール、コードカバレッジツールなどとともに、控えめに使用する必要があります。


3
ほとんどのprintf()実装は、スレッドセーフ(つまり、再入不可)ではなく、キラーではありませんが、マルチスレッド環境で使用する場合に留意する必要があります。
JRobert

1
@JRobertは良い点をもたらします。そして、オペレーティングシステムのない環境でさえ、ISRの便利な直接デバッグを行うことは困難です。もちろん、ISRでprintf()または浮動小数点演算を実行している場合、おそらくアプローチはオフになっています。
スペロペファニー14

@JRobertマルチスレッド環境(ロジックアナライザーとオシロスコープの使用が実用的ではないハードウェア設定)で作業しているソフトウェア開発者は、どのデバッグツールを使用していますか?
ミン・トラン

1
過去に、私は自分のスレッドセーフなprintf()を使用しました。裸足のputs()またはputchar()に相当するものを使用して、非常に簡潔なデータを端末に吐き出しました。テスト実行後にダンプおよび解釈した配列にバイナリデータを格納しました。I / Oポートを使用して、LEDを点滅させたり、オシロスコープでタイミング測定を行うためのパルスを生成したりしました。D / Aに数字を吐き出し、VOMで測定します...リストはあなたの想像と同じくらい長く、逆に予算と同じくらいです!:)
JRobert

19

いくつかの他の良い答えに加えて、シリアルボーレートでポートにデータを送信する行為は、ループ時間に関してまったく遅くなる可能性があり、プログラムの残りの機能に影響を与える可能性があります処理する)。

他の人々があなたに言っているように、このテクニックを使用することについて「悪い」ことは何もありませんが、他の多くのデバッグテクニックのように、その制限があります。これらの制限を知っていて対処できる限り、コードを正しく取得するのに非常に便利なアフォーダンスになります。

組み込みシステムには特定の不透明度があり、一般にデバッグが少し問題になります。


8
「組み込みシステムには特定の不透明度があります」の場合は+1。この声明は、組み込みに関する適切な経験がある人だけが理解できるものではないかと心配していますが、状況の簡潔で簡潔な要約になります。実際には「埋め込み」の定義に近い。
njahnke

5

printfマイクロコントローラーで使用しようとすると、主に2つの問題が発生します。

まず、出力を正しいポートにパイプするのは苦痛です。常にではない。しかし、一部のプラットフォームは他のプラットフォームよりも困難です。構成ファイルの一部は文書化が不十分な場合があり、多くの実験が必要になる場合があります。

2つ目はメモリです。本格的なprintfライブラリは大きくなる可能性があります。ただし、すべての形式指定子が必要なわけではなく、特殊なバージョンが利用できる場合もあります。たとえば、stdio.h AVRによって提供されるにprintfは、さまざまなサイズと機能の3つの異なるが含まれます。

上記のすべての機能の完全な実装がかなり大きくなるため、vfprintf()リンカーオプションを使用して3つの異なるフレーバーを選択できます。デフォルトでvfprintf()は、浮動小数点変換を除く上記のすべての機能が実装されます。vfprintf()非常に基本的な整数および文字列変換機能のみを実装する最小化されたバージョンが利用可能ですが、#変換フラグを使用して追加オプションのみを指定できます(これらのフラグはフォーマット仕様から正しく解析されますが、単に無視されます)。

ライブラリが利用できず、メモリが最小のインスタンスがありました。そのため、カスタムマクロを使用する以外に選択肢はありませんでした。しかし、printf実際に使用するかどうかは、要件に適合するものの1つです。


今後のプロジェクトで私の間違いを避けるために、ダウンボッターは私の答えが間違っていることを説明してもらえますか?
embedded.kyle 14

4

Spehro Pefhanyが「タイミングに敏感なもの」について言っていたことに加えて、例を見てみましょう。組み込みシステムが毎秒1,000回の測定を行うジャイロスコープがあるとします。これらの測定値をデバッグするため、印刷する必要があります。問題:それらを印刷すると、システムがビジー状態になり、1秒あたり1,000回の測定値を読み取ることができなくなります。これにより、ジャイロスコープのバッファーがオーバーフローし、破損したデータが読み取られます(印刷されます)。そのため、データを印刷することでデータが破損し、実際にはデータがない場合でもデータの読み取りにバグがあると考えるようになります。いわゆるヘイゼンバグ。


笑!「heisenbug」は本当に専門用語ですか?私は...それは粒子状態とHeisenburgの原理の測定に関係していると思います
Zeta.Investigator

3

printf()を使用してデバッグしない大きな理由は、通常、非効率的で、不十分で、不要なためです。

非効率:printf()とkinは、小さなマイクロコントローラーで利用可能なものと比較して、多くのフラッシュとRAMを使用しますが、実際のデバッグでは大きな非効率があります。ログに記録される内容を変更するには、ターゲットを再コンパイルおよび再プログラミングする必要があり、プロセスが遅くなります。また、有用な作業を行うために使用できるUARTを使い果たします。

不十分:シリアルリンクを介して出力できる詳細はあまりありません。プログラムがハングした場合、正確な場所はわからず、完了した最後の出力だけがわかります。

不要:多くのマイクロコントローラーをリモートでデバッグできます。JTAGまたは独自のプロトコルを使用して、プロセッサを一時停止したり、レジスタやRAMを覗いたり、再コンパイルすることなく実行中のプロセッサの状態を変更することもできます。これが、多くのスペースとパワーを備えたPC上であっても、一般にデバッガーがprintステートメントよりもデバッグに適している理由です。

初心者向けの最も一般的なマイクロコントローラプラットフォームであるArduinoにデバッガがないのは残念です。AVRはリモートデバッグをサポートしていますが、AtmelのdebugWIREプロトコルは独自仕様であり、文書化されていません。公式の開発ボードを使用してGDBでデバッグできますが、それがあれば、おそらくArduinoについてはあまり心配していません。


関数ポインターを使用して、ログに記録されているものを再生し、柔軟性を大幅に追加できませんでしたか?
スコットサイドマン14

3

printf()は単独では機能しません。他の多くの関数を呼び出します。スタックスペースが少ない場合は、スタック制限に近い問題をデバッグするためにまったく使用できない場合があります。コンパイラとマイクロコントローラによっては、フラッシュから参照されるのではなく、フォーマット文字列がメモリに配置される場合もあります。コードにprintfステートメントを追加すると、これが大幅に増える可能性があります。これはArduino環境での大きな問題です。数十または数百のprintfステートメントを使用する初心者は、スタックでヒープを上書きしているため、突然ランダムに見える問題に遭遇します。


2
ダウン投票自体が提供するフィードバックには感謝しますが、意見が合わない人がこの回答の問題を説明してくれれば、私や他の人にとってより役立つでしょう。私たちは皆、知識を学び共有するためにここにいます。あなたのものを共有することを検討してください。
アダムデイビス

3

何らかの形式のロギングコンソールにデータを吐き出したい場合でもprintf、渡されたフォーマット文字列を調べて実行時に解析する必要があるため、この関数は一般的にあまり良い方法ではありません。コードが以外の形式指定子を使用しない場合でも%04X、コントローラーは通常、任意の形式文字列を解析するために必要なすべてのコードを含める必要があります。使用している正確なコントローラーに応じて、次のようなコードを使用する方がはるかに効率的です。

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

いくつかのPICマイクロコントローラでは、log_hexi32(l)(あればおそらく9つの手順を取ると17を取るかもしれないl第二バンクである)、しばらくはlog_hexi32p(&l)2を取るlog_hexi32p二回呼び出された場合、それは自分自身のために支払うことになるので、機能自体は、長い約14の指示であることを書くことができます。


2

他の答えのどれも言及していない1つのポイント:クラッシュ/停止/取得する場合、基本的なマイクロ(IEにはメイン()ループとおそらく2つのISRが常に実行されていますが、マルチスレッドOSではありません)ループでスタックしている場合、印刷機能は発生しません

また、人々は「printfを使用しない」または「stdio.hは多くのスペースを必要とします」と言っていますが、あまり多くの選択肢はありません。embedded.kyleは単純化された選択肢について言及していますもちろん、基本的な組み込みシステムで実行します。UARTから数文字を噴出するための基本的なルーチンは、数バイトのコードです。


printfが発生しない場合は、コードのどこに問題があるかについて多くを学びました。
スコットシドマン14

発生するかもしれないprintfが1つしかないと仮定すると、はい。ただし、UARTから何かを取得するためにprintf()呼び出しにかかる時間内に、割り込みが何百回も発生する可能性があります
ジョンU 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.