printf-バグの原因?[閉まっている]


9

私はprintfコードでトレース/ロギングの目的で多くを使用していますが、それがプログラミングエラーの原因であることがわかりました。私は常に挿入演算子(<<)が少し奇妙なものであることに気付きましたが、代わりにそれを使用することでこれらのバグのいくつかを回避できると思い始めています。

誰かが似たような啓示を受けたことがありますか、ここでストローを掴んでいるだけですか?

いくつかのポイントを奪う

  • 私の現在の考え方は、型安全性はprintfを使用する利点よりも優れているということです。本当の問題は、フォーマット文字列とタイプセーフでない可変長関数の使用です。
  • 多分私は使用せ<<ず、stl出力ストリームのバリアントを使用しますが、非常によく似たタイプセーフメカニズムの使用を検討します。
  • トレース/ロギングの多くは条件付きですが、めったに分岐しないという理由だけで、テストのバグを見逃さないように常にコードを実行したいと思います。

4
printfC ++の世界では?ここで何か不足していますか?
user827992 2012

10
@ user827992:C ++標準に参照によってC標準ライブラリが含まれているという事実を見逃していますか?printfC ++での使用は完全に合法です。(それが良いアイデアかどうかは別の質問です。)
キース・トンプソン

2
@ user827992:printfいくつかの利点があります。私の答えを見てください。
キーストンプソン

1
この質問はかなり境界線です。「皆さんはどう思いますか」という質問は、しばしば閉じられます。
dbracey 2012

1
@vitaut私は推測します(ヒントをありがとう)。私は積極的な節度に少し戸惑っています。それは私がもっと知りたいプログラミング状況についての興味深い議論を実際に促進するものではありません。
John Leidegren、2015年

回答:


2

printfは、特にパフォーマンスを気にする可能性がある場合(sprintfやfprintfなど)は、非常に奇妙なハックです。仮想関数に関連するパフォーマンスのオーバーヘッドが非常に小さいためにC ++に打ち勝つ人々は、Cのioを防御し続けることに常に驚かされます。

はい、出力のフォーマットを理解するために、コンパイル時に100%知ることができるものです。実行不能なフォーマットコードを使用して、非常に奇妙なジャンプテーブル内で実行時に壊れたフォーマット文字列を解析しましょう。

もちろん、これらのフォーマットコードを、それらが表すタイプに一致させることはできなかったので、それは非常に簡単です...そして、この(厳密にタイプされた)言語があなたを作る%llgか%lgかを検索するたびに思い出されますタイプを手動で計算して何かを印刷/スキャンし、32ビット以前のプロセッサ用に設計されました。

C ++のフォーマット幅と精度の処理はかさばり、構文上の砂糖を使用する可能性があることは認めますが、それは、Cのメインioシステムである奇妙なハックを防御する必要があることを意味しません。絶対的な基本はどちらの言語でもかなり簡単です(おそらくデバッグコードにカスタムエラー関数/エラーストリームのようなものを使用する必要があります)、中程度のケースはCの正規表現に似ています(書きやすく、解析/デバッグが難しい) )、Cでは不可能な複雑なケース

(標準のコンテナーを使用する場合std::cout << my_list << "\n";は、my_listがtypeである場合に、デバッグのようなことを可能にする簡単なテンプレートオペレーター<<オーバーロードを自分で作成しますlist<vector<pair<int,string> > >。)


1
標準C ++ライブラリの問題は、ほとんどのインカネーションoperator<<(ostream&, T)が呼び出しによって実装されることです...まあ、sprintf!のパフォーマンスsprintfは最適ではありませんが、これが原因で、iostreamのパフォーマンスは一般にさらに悪化します。
Jan Hudec 2012

@JanHudec:現時点では、それは約10年間真実ではありません。実際の印刷は、同じ基本的なシステムコールで行われ、C ++実装はそのためにCライブラリを呼び出すことがよくあります...しかし、これは、std :: coutをprintfでルーティングすることと同じではありません。
jkerian 2012

16

Cスタイルprintf()(またはputs()or putchar()または...)の出力とC ++スタイルのstd::cout << ...出力を混在させると安全でない場合があります。私が正しく思い出せば、バッファリングメカニズムが別々になる可能性があるため、出力が意図した順序で表示されない可能性があります。(AProgrammerがコメントで言及しているように、sync_with_stdioこれに対処します)。

printf()基本的にタイプセーフではありません。引数に期待されるタイプは、フォーマット文字列によって決定されます("%d"必要とintする促進または何かをint"%s"必要としchar*ますが、未定義の動作に引数結果の間違った種類を渡して正しく終端Cスタイルの文字列などを指している必要があるが) 、診断可能なエラーではありません。gccなどの一部のコンパイラーは、型の不一致についてかなり適切に警告を出しますが、フォーマット文字列がリテラルであるか、コンパイル時に既知である場合(これが最も一般的なケースです)にのみ可能です-など言語は警告を要求しません。間違ったタイプの引数を渡すと、恣意的に悪いことが起こる可能性があります。

一方、C ++のストリームI / Oは、<<演算子が多くの異なる型に対してオーバーロードされるため、はるかに型安全です。std::cout << xのタイプを指定する必要はありませんx。コンパイラーは、どんな型でも正しいコードを生成しxます。

一方、printfのフォーマットオプションは、IMHOの方がはるかに便利です。小数点以下3桁の浮動小数点値を出力する場合は、"%.3f"-を使用できますprintf。同じ呼び出し内でも、他の引数には影響しません。setprecision一方、C ++ はストリームの状態に影響を与え、ストリームを以前の状態に復元するのに細心の注意を払わなければ、後の出力を台無しにする可能性があります。(これは私の個人的な不満です。回避するためのクリーンな方法がない場合は、コメントしてください。)

どちらにも長所と短所があります。の可用性は、printfたまたまCのバックグラウンドがあり、それに慣れている場合、またはCのソースコードをC ++プログラムにインポートする場合に特に役立ちます。std::cout << ...C ++ではより慣用的であり、型の不一致を避けるためにそれほど注意する必要はありません。どちらも有効なC ++です(C ++標準には、参照によりほとんどのC標準ライブラリが含まれています)。

コードで作業する他のC ++プログラマーのために使用するのがおそらく最善std::cout << ...ですが、どちらか一方を使用することができます。特に、破棄する予定のトレースコードでは使用できます。

そしてもちろん、デバッガを使用する方法を学ぶのに少し時間を費やす価値があります(ただし、環境によっては実行できない場合もあります)。


元の質問での混合についての言及はありません。
dbracey 2012

1
@dbracey:いいえ。ただし、の潜在的な欠点として言及する価値があると思いましたprintf
Keith Thompson、

6
同期の問題については、を参照してくださいstd::ios_base::sync_with_stdio
AProgrammer 2012

1
+1 std :: coutを使用してマルチスレッドアプリでデバッグ情報を出力することは、100%役に立ちません。少なくともprintfでは、物事がインターリーブされたり、人や機械によって解析されたりする可能性は低くなります。
ジェームズ

@ジェームズ:std::cout印刷されるアイテムごとに個別の呼び出しを使用しているためですか?印刷する前に出力の行を文字列に収集することで、これを回避できます。そしてもちろん、を使用して一度に1つのアイテムを印刷することもできprintfます。1回の呼び出しで1行(または複数)を印刷する方が便利です。
キーストンプソン

2

ほとんどの場合、問題は2つの非常に異なる標準出力マネージャーが混在しているために発生します。それぞれの標準出力マネージャーには、その貧弱なSTDOUTに対する独自の課題があります。挿入演算子は一つの大きなオーバーを持って、またなど、これまでさまざまなことを行うために、両方試して、あなたは彼らが実装されている方法についての保証を取得していない、そしてファイルディスクリプタのオプションが競合していることは完全に可能であるprintfprintfあなたがこれを行うようになります。

printf("%d", SomeObject);

<<ませんが。

注:デバッグには、printfまたはを使用しませんcout。とを使用fprintf(stderr, ...)cerrます。


元の質問での混合についての言及はありません。
dbracey 2012

もちろん、オブジェクトのアドレスを出力することもできますが、大きな違いはprintfタイプセーフではないことです。現在の考え方では、タイプセーフはを使用する利点よりも優れているということですprintf。問題は、実際にはフォーマット文字列であり、タイプセーフな可変関数ではありません。
ジョンライデグレン

@JohnLeidegren:しかしSomeObject、ポインタでない場合はどうなりますか?コンパイラが決定する任意のバイナリデータを取得しようとしていますSomeObject
Linuxios 2012

私はあなたの答えを逆に読んだと思います... nvm。
ジョンライデグレン

1

ストリームが嫌いなグループがたくさんあります-例えばgoogle-。

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(あなたが議論を見ることができるように三角形のものをポップオープンしてください。)私はグーグルC ++スタイルガイドに非常に賢明なアドバイスがたくさんあると思います。

トレードオフは、ストリームの方が安全ですが、printfの方が読みやすい(そして、必要な書式を正確に取得するのが簡単)だと思います。


2
Googleスタイルガイドはすばらしいですが、汎用ガイドには適さないアイテムがかなり含まれています。(結局それは大丈夫です。結局のところ、Googleで実行されているコードに関する Googleのガイドだからです。)
Martin Ba

1

printf型安全性の欠如によりバグを引き起こす可能性があります。iostream<<演算子に切り替えずに、より複雑なフォーマットに対処する方法はいくつかあります。

  • 一部のコンパイラ(GCCやClangなど)は、オプションで引数printfに対してフォーマット文字列をチェックprintfでき、一致しない場合は次のような警告を表示できます。
    警告:変換で型 'int'が指定されていますが、引数の型は 'char *'です
  • typesafeprintfのスクリプトは、前処理することができprintf、それらは、タイプセーフにするためにスタイルの呼び出しを。
  • Boost.FormatFastFormatなどのライブラリを使用すると、型の安全性と型の拡張性を維持しながらprintf、のような書式文字列(特にBoost.Formatはとほぼ同じですprintf)を使用できますiostreams

1

Printf構文は基本的には問題ありませんが、あいまいなタイピングの一部は除きます。C#、Python、その他の言語が非常によく似た構成を使用する理由が間違っていると思われる場合は、CまたはC ++の問題:言語の一部ではないため、コンパイラーによって正しい構文(*)がチェックされず、速度を最適化した場合に一連のネイティブ呼び出しに分解されません。サイズを最適化すると、printf呼び出しがより効率的になる可能性があることに注意してください。C ++ストリーミング構文は、良いとは言えません。それは機能し、タイプセーフはありますが、冗長な構文です... 私はそれを使うことを意味しますが、喜びはありません。

(*)一部のコンパイラは、このチェックとほとんどすべての静的分析ツールを実行します(私はLintを使用しており、それ以来、printfで問題が発生していません)。


1
便利な構文()とタイプセーフを組み合わせたBoost.Formatがありますformat("fmt") % arg1 % arg2 ...;。多くの実装で内部的にsprintf呼び出しを生成するstringstream呼び出しを生成するため、パフォーマンスがいくらか犠牲になります。
Jan Hudec 2012

0

printf私の意見では、変数を処理するためのCPPストリーム出力よりもはるかに柔軟な出力ツールです。例えば:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

ただし、CPP <<演算子を使用する必要があるのは、特定のメソッドに対してオーバーロードする場合です。たとえば、特定の人物のデータを保持するオブジェクトのダンプを取得する場合などPersonDataです。

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

そのため、(aPersonDataのオブジェクトであると仮定して)言う方がはるかに効果的です。

std::cout << a;

より:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

前者はカプセル化の原則にはるかに沿っており(詳細、プライベートメンバー変数を知る必要はありません)、読みやすくなっています。


0

printfC ++での使用は想定されていません。ずっと。その理由は、あなたが正しく述べたように、それがバグの原因であり、カスタムタイプの印刷、そしてC ++ではほとんどすべてがカスタムタイプであるべきであるという事実が苦痛であるということです。C ++ソリューションはストリームです。

ただし、ストリームをユーザーに表示されるすべての出力に不適切にする重大な問題があります。問題は翻訳です。gettextマニュアルの借用例は、次のように記述したい場合です。

cout << "String '" << str << "' has " << str.size() << " characters\n";

さて、ドイツ語の翻訳者がやって来て言います:わかりました、ドイツ語では、メッセージは

n Zeichen lang ist die Zeichenkette ' s '

そして今、あなたは困っています。彼はピースをシャッフルする必要があるからです。の多くの実装でprintfもこれに問題があると言われるべきです。あなたが使用できるように拡張機能をサポートしない限り

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Boost.Formatは printf形式のフォーマットをサポートしており、この機能を持っています。だからあなたは書く:

cout << format("String '%1' has %2 characters\n") % str % str.size();

残念ながら、内部的に文字列ストリームを作成し、<<演算子を使用して各ビットをフォーマットし、多くの実装では<<演算子が内部的にを呼び出すため、パフォーマンスが少し低下しますsprintf。本当に望めば、もっと効率的な実装が可能になると思います。


-1

あなたは、実際のそばに、役に立たない多くの作業を行っているstl、悪かないでデバッグする一連のコードをprintf唯一可能な障害の1つの以上のレベルを追加します。

デバッガを使用して、例外について何かを読んで、それらをキャッチしてスローする方法を読んでください。実際に必要以上に冗長にならないようにしてください。

PS

printf あなたが持っているC ++のために、Cで使用されています std::cout


デバッガの代わりにトレース/ログを使用しません。
ジョンライデグレン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.