CおよびC++プログラムをコンパイルするとき、「コンパイラの警告を常に有効にする」必要があるとよく耳にします。なぜこれが必要なのですか?それ、どうやったら出来るの?
「警告をエラーとして扱う」べきだという話も時々聞きます。したほうがいい?それ、どうやったら出来るの?
Cは、HLLのように、かなり低レベルの言語であることが有名です。 C++は、Cよりもかなり高レベルの言語のように見えるかもしれませんが、それでも多くの特徴を共有しています。そして、それらの特徴の1つは、言語がプログラマー、プログラマー、そして具体的には、彼らが何をしていたかを知っているプログラマーによって設計されたということです。
[この回答の残りの部分では、Cに焦点を当てます。私が言うことのほとんどは、C++にも当てはまりますが、おそらくそれほど強くはありません。 Bjarne Stroustrupが有名に言ったように、 「Cは足で自分を撃ちやすくします。C++は難しくしますが、それを行うと足全体が吹き飛ばされます。」 ]
あなたが何をしているのかを知っている場合本当にあなたが何をしているのかを知っている-時には「ルールを破る」必要があるかもしれません。しかし、ほとんどの場合、私たちのほとんどは、善意のルールが私たちすべてをトラブルから守ってくれること、そして常にこれらのルールを破ることだけが悪い考えだということに同意するでしょう。
しかし、CおよびC++では、「悪い考え」であるが正式には「ルールに反する」ことではない、驚くほど多くのことができます。時々、彼らは時々悪い考えです(しかし、他の時には防御できるかもしれません)。時々、それらは事実上常に悪い考えです。しかし、伝統は常にこれらのことについて警告するためにnotでした-繰り返しますが、前提はプログラマーが何をしているのか知っており、正当な理由なしにこれらのことをしないということですたくさんの不必要な警告に悩まされるでしょう。
しかし、もちろんすべてのプログラマー本当に彼らが何をしているかを知っているわけではありません。そして、特に、すべてのCプログラマー(経験豊富な人でも)は、最初のCプログラマーになる段階を経ます。そして、経験豊富なCプログラマでさえ、不注意になり、ミスを犯す可能性があります。
最後に、経験から、プログラマーが間違いを犯すだけでなく、これらの間違いが実際に深刻な結果をもたらす可能性があることが示されています。あなたが間違いを犯し、コンパイラがそれについて警告せず、どういうわけかプログラムがすぐにクラッシュしたり、明らかにそれによって何か悪いことをしたりしない場合、間違いはそこに潜むことがあります。 本当に大きな問題。
結局のところ、ほとんどの場合、結局のところ、警告は良いアイデアであることがわかります。経験豊富なプログラマーでさえも(実際、「特に経験豊富なプログラマーが学んだ」)、バランスをとると、警告は害よりも良いことをする傾向があることを学びました。あなたが故意に何か間違ったことをし、警告が迷惑だったたびに、おそらくあなたは偶然に何か間違ったことをし、警告はあなたをさらなるトラブルから救ったでしょう。そして、あなたが本当に「間違った」ことをしたいときは、ほとんどの警告を無効にしたり、回避したりすることができます。
(そのような「間違い」の典型的な例は、テストif(a = b)
です。ほとんどの場合、これは間違いであるため、最近ではほとんどのコンパイラーが警告しています。デフォルトでも一部です。 reallyb
にa
を割り当て、結果をテストしたい場合は、if((a = b))
と入力して警告を無効にできます。
2番目の質問は、なぜ警告をエラーとして扱うようコンパイラーに依頼したいのですか?私はそれが人間の性質、具体的には、「ああ、それは単なる警告であり、それほど重要ではない、私は後でそれをきれいにする」という非常に簡単な反応のためだと思います。しかし、あなたが先延ばしであるなら(そして私はあなたのことを知らないが、私はterrible先延ばし者である)、それは基本的にこれまでに必要なクリーンアップを延期するのは簡単です-そしてあなたが入れば警告を無視する習慣により、無視しているすべての警告の中で、気付かれずにそこに座っているimportant警告メッセージを見逃しやすくなります。
そのため、警告をエラーとして扱うようコンパイラーに依頼することは、この人間の脆弱性を回避するために自分で遊ぶことができるちょっとしたトリックです。
個人的に、私は警告をエラーとして扱うことを主張していません。 (実際、正直なところ、「パーソナル」プログラミングでそのオプションを実質的に有効にしたことはありません。)しかし、私たちのスタイルガイド(私は書きました)その使用を義務付けています。そして、私は言う-私はほとんどのプロのプログラマーが言うだろう-Cのエラーとして警告を扱っていない店は無責任に動作しており、一般的に受け入れられている業界のベストプラクティスを遵守していないと言う。
警告は、いくつかの最も熟練したC++開発者がアプリケーションに焼き付けることができる最良のアドバイスで構成されています。それらは維持する価値があります。
チューリング完全言語であるC++には、何をしているのかを知っていることをコンパイラが単純に信頼しなければならないケースがたくさんあります。ただし、自分が書いたものを書こうとは思わなかったことをコンパイラが認識できる場合が多くあります。古典的な例は、引数に一致しないprintf()コード、またはprintfに渡されるstd :: stringsです(everが私に起こることではありません!)。これらの場合、作成したコードはエラーではありません。これは、コンパイラーが動作するための有効な解釈を備えた有効なC++式です。しかし、コンパイラーには、現代のコンパイラーが検出しやすい何かを見落としているという強い予感があります。これらは警告です。これらは、コンパイラーにとって明らかなものであり、C++のすべての厳密な規則を自由に使用して、見落としているかもしれません。
警告をオフにしたり無視したりすることは、あなたよりも熟練した人からの無料のアドバイスを無視することに似ています。あなたが太陽に近づきすぎて翼が溶けるか、メモリ破損エラーが発生したときに終わる、フベリスのレッスン。二人の間で、私はいつでも空から落ちます!
「警告をエラーとして扱う」は、この哲学の極端なバージョンです。ここでのアイデアは、解決することですeveryコンパイラが警告を与える-あなたはあらゆる自由なアドバイスに耳を傾け、それに基づいて行動します。これが開発に適したモデルであるかどうかは、チームと作業している製品の種類によって異なります。それは修道士が持っているかもしれない禁欲的なアプローチです。一部の人にとっては、うまく機能します。他の人にとってはそうではありません。
私のアプリケーションの多くでは、警告をエラーとして扱いません。これらの特定のアプリケーションは、さまざまな年齢の複数のコンパイラを使用して複数のプラットフォームでコンパイルする必要があるため、これを行います。別のプラットフォームで警告に変わることなく、一方の警告を修正することは実際には不可能であることがわかります。ですから、私たちはただ注意しています。私たちは警告を尊重しますが、私たちはそれらの警告を無視しません。
警告を処理することでコードが改善されるだけでなく、優れたプログラマーになります。警告は、今日あなたにはほとんど見えないかもしれないことについて教えてくれますが、ある日、その悪い習慣が戻ってきて、頭をかむでしょう。
正しいタイプを使用し、その値を返し、その戻り値を評価します。時間をかけて、「これは本当にこのコンテキストで正しいタイプですか?」 「これを返す必要がありますか?」そして大物。 「このコードは今後10年間、移植可能になるのでしょうか?」
そもそも警告のないコードを書く習慣を身につけてください。
他の答えは素晴らしく、彼らが言ったことを繰り返したくありません。
適切に触れられていない「警告を有効にする理由」のもう1つの側面は、コードのメンテナンスに非常に役立つことです。かなりのサイズのプログラムを作成すると、一度にすべてを頭の中に収めることができなくなります。通常、あなたが積極的に書いて考えている機能または3つ、そしておそらくあなたが参照できるファイルまたは3つが画面上にありますが、プログラムの大部分はどこかにバックグラウンドで存在し、それを信頼する必要があります働き続けます。
警告をオンにして、できる限り精力的で顔に警告を表示すると、変更したものが見えないものに問題を起こした場合に警告するのに役立ちます。
たとえば、clang警告-Wswitch-enum
。列挙型でスイッチを使用し、可能な列挙型値の1つを逃すと、警告がトリガーされます。それはあなたが犯すことはありそうもない間違いだと思うかもしれない何かです:あなたはおそらくあなたが少なくともswitch文を書いたときに列挙値のリストを見ました。スイッチオプションを生成するIDEがあり、人為的エラーの余地がない場合もあります。
この警告は、6か月後に列挙に別の可能なエントリを追加すると実際に発生します。繰り返しますが、問題のコードについて考えているなら、おそらく大丈夫でしょう。しかし、この列挙型が複数の異なる目的で使用され、追加オプションが必要な場合は、6か月間触れていないファイルのスイッチを更新するのを忘れがちです。
警告は、自動化されたテストケースの場合と同じように考えることができます。これらは、コードが適切であり、最初に記述するときに必要なことを実行することを確認するのに役立ちますが、必要なことをやり続けます。違いは、テストケースがコードの要件に対して非常に狭い範囲で動作するため、それらを記述する必要があることです。一方、警告は、ほぼすべてのコードの賢明な標準に対して広く機能し、コンパイラを作成するボフィンによって非常に寛大に提供されます。
たとえば、セグメンテーションフォールトをデバッグするには、プログラマーがフォールトのルート(原因)をトレースする必要があります。通常、これは、最終的にセグメンテーションフォールトを引き起こした行よりもコード内の前の場所にあります。
原因は、コンパイラが無視した警告を発行した行であり、セグメンテーション違反の原因となった行が最終的にエラーをスローした行であることが非常に一般的です。
警告を修正すると、問題が修正されます。
上記のデモンストレーションです。次のコードを検討してください。
#include <stdio.h>
int main(void) {
char* str = "Hello world!";
int idx;
// Colossal amount of code here, irrelevant to 'idx'
printf("%c\n", str[idx]);
return 0;
}
gCCに渡される「Wextra」フラグを使用してコンパイルすると、次のようになります。
main.c: In function 'main':
main.c:9:21: warning: 'idx' is used uninitialized in this function [-Wuninitialized]
9 | printf("%c\n", str[idx]);
| ^
どのIcouldとにかくコードを無視して実行します。そして、IP epicurus教授が言っていたように、「グランド」セグメンテーションフォールトを目撃します。
セグメンテーション障害
これを実世界のシナリオでデバッグするには、セグメンテーションフォールトの原因となっている行から開始し、原因の原因を追跡しようとします。彼らはその中のi
とstr
に何が起こったかを検索する必要があります。膨大な量のコードが...
ある日、彼らはidx
が初期化されずに使用されていることを発見した状況で自分自身を見つけたので、ガベージ値を持っているため、文字列(範囲)の境界を超えてインデックス付けされ、セグメンテーションフォールトに至ります。
彼らが警告を無視していなかったなら、彼らはすぐにバグを見つけたでしょう!
警告をエラーとして扱うことは、自己鍛錬の手段に過ぎません。その輝かしい新機能をテストするためにプログラムをコンパイルしていましたが、ずさんな部分を修正するまでできませんです。 Werror
が提供する追加情報はなく、優先度を非常に明確に設定するだけです。
既存のコードの問題を修正するまで、新しいコードを追加しないでください
重要なのは、ツールではなく、本当に考え方です。コンパイラ診断出力はツールです。 MISRA(組み込みC用)は別のツールです。どちらを使用するかは関係ありませんが、おそらくコンパイラ警告は最も簡単に入手できるツールであり(設定するフラグは1つだけです)、S/N比は非常に高くなっています。したがって、使用する理由はありませんnot。
確実なツールはありません。 _const float pi = 3.14;
_と書くと、ほとんどのツールは、将来の問題につながる可能性のある精度の悪いπを定義したことを通知しません。ほとんどのツールは、if(tmp < 42)
に眉をひそめませんが、変数に意味のない名前を付けたり、マジックナンバーを使用したりすることは、大きなプロジェクトで大惨事を招くということが一般的に知られています。 あなたは、あなたが書く「クイックテスト」コードはそれだけであるということを理解する必要があります:テスト、そして次に進む前にそれを手に入れなければなりません他のタスク、まだその欠点を見ながら。そのコードをそのままにしておくと、2か月かけて新しい機能を追加した場合のデバッグが大幅に難しくなります。
正しい考え方に慣れたら、Werror
を使用しても意味がありません。警告を警告として持つことにより、開始しようとしていたデバッグセッションを実行するのが理にかなっているか、それを中止して警告を最初に修正するかについて、十分な情報に基づいた決定を下すことができます。
レガシーの埋め込みCコードを扱う人として、コンパイラの警告を有効にすると、修正を提案する際に多くの弱点と調査すべき領域を示すことができました。 gccでは、-Wall
と-Wextra
、さらには-Wshadow
を活用することが重要になりました。すべてのハザードに行くわけではありませんが、コードの問題を示すのに役立つポップアップをいくつかリストします。
これは、未完成の作業と、問題となる可能性のある渡された変数のすべてを利用していない可能性のある領域を簡単に指すことができます。これをトリガーする単純な関数を見てみましょう。
int foo(int a, int b)
{
int c = 0;
if (a > 0)
{
return a;
}
return 0;
}
-Wallまたは-Wextraを使用せずにこれをコンパイルするだけで問題は発生しません。 -Wallは、c
が決して使用されないことを通知します:
foo.c:関数「foo」内:
foo.c:9:20:警告:未使用変数 'c' [-Wunused-variable]
-Wextraは、パラメーターbが何もしないことも通知します。
foo.c:関数「foo」内:
foo.c:9:20:警告:未使用変数 'c' [-Wunused-variable]
foo.c:7:20:警告:未使用パラメーター「b」[-Wunused-parameter] int foo(int a、int b)
これは少し難しく、-Wshadow
が使用されるまで表示されませんでした。上記の例を修正して追加するだけですが、ローカルと同じ名前のグローバルがあり、両方を使用しようとすると多くの混乱が生じます。
int c = 7;
int foo(int a, int b)
{
int c = a + b;
return c;
}
-Wshadowがオンになっていると、この問題を簡単に見つけることができます。
foo.c:11:9:警告: 'c'の宣言はグローバル宣言を隠します[-Wshadow]
foo.c:1:5:注:影付きの宣言はこちら
これにはgccで追加のフラグは必要ありませんが、過去の問題の原因となっています。データを印刷しようとする単純な関数ですが、フォーマットエラーは次のようになります。
void foo(const char * str)
{
printf("str = %d\n", str);
}
書式設定フラグが間違っているため、これは文字列を出力しません。gccはこれがおそらくあなたが望んでいたものではないことを喜んで伝えます:
foo.c:関数「foo」内:
foo.c:10:12:警告:フォーマット '%d'はタイプ 'int'の引数を必要としますが、引数2はタイプ 'const char *' [-Wformat =]
これらは、コンパイラーがあなたのために二重にチェックできる多くのことのほんの3つです。他の人が指摘した初期化されていない変数の使用など、他にもたくさんあります。
これはCに対する具体的な回答であり、これが他の何よりもCにとってはるかに重要である理由です。
#include <stdio.h>
int main()
{
FILE *fp = "some string";
}
このコードはwarningでコンパイルされます。惑星上の他のほぼすべての言語(アセンブリ言語を除く)のエラーとは何ですか(アセンブリ言語を除く)は、Cではwarningsです。警告は抑制されるのではなく、修正する必要があります。
gcc
を使用すると、これをgcc -Wall -Werror
。
これは、一部のMS非セキュアAPI警告に関する高い保証の理由でもありました。 Cをプログラミングするほとんどの人は、警告をエラーとして扱う難しい方法を学びました。このようなものは、同じ種類のものではなく、移植性のない修正を望んでいました。
C++のコンパイラ警告は、いくつかの理由で非常に役立ちます。
1-操作の最終結果に影響を与える可能性のあるミスを犯した可能性がある場所を示すことができます。たとえば、変数を初期化していない場合、または「==」の代わりに「=」を配置した場合(例のみがあります)
2-コードがC++の標準に準拠していない場所を示すこともできます。コードが実際の標準に準拠している場合、たとえばコードを他のプレートフォームに簡単に移動できるため便利です。
一般に、警告は、アルゴリズムの結果に影響を与えたり、ユーザーがプログラムを使用するときにエラーを防ぐことができるコードの誤りがある場所を示すのに非常に役立ちます。
警告を無視するということは、将来他の誰かに問題を引き起こす可能性があるだけでなく、重要なコンパイルメッセージが気付かれにくくなるような、ずさんなコードを残したことを意味します。コンパイラの出力が多いほど、誰も気づかないか気になりません。よりきれいに。また、自分が何をしているかを知っていることも意味します。警告は非常に専門的ではなく、不注意で危険です。
コンパイラの警告はあなたの友だちです(叫び声ではなく、強調のために大文字)。
私はレガシーのFortran-77システムで働いています。コンパイラーは価値のあることを教えてくれます:使用されていない変数またはサブルーチン引数がある場合、値が変数に設定される前にローカル変数を使用して、サブルーチン呼び出しでの引数データ型の不一致。これらはほとんどの場合エラーです。
長い投稿の回避:コードが正常にコンパイルされると、97%動作します。私が働いているもう一人の男は、すべての警告をオフにしてコンパイルし、デバッガーで数時間または数日を費やし、助けを求めます。警告をオンにして彼のコードをコンパイルし、何を修正するかを彼に伝えるだけです。
警告は、発生を待機しているエラーです。したがって、コンパイラの警告を有効にし、コードを整理して警告を削除する必要があります。
コンパイラはコードの何が問題なのかを頻繁に通知することがあるため、常にコンパイラの警告を有効にする必要があります。これを行うには、-Wall -Wextra
をコンパイラに渡します。
通常、警告はコードに何らかの問題があることを意味するため、警告はエラーとして扱う必要があります。ただし、多くの場合、これらのエラーを無視するのは非常に簡単です。したがって、それらをエラーとして扱うとビルドが失敗するため、エラーを無視することはできません。警告をエラーとして扱うには、-Werror
をコンパイラに渡します。
私はかつて、電子テスト機器を製造する大企業(Fortune 50)で働いていました。
私のグループのコア製品は、長年にわたって文字通り何百もの警告を生成するようになったMFCプログラムでした。ほとんどすべてのケースで無視されました。
これは、バグが発生した場合の気難しい悪夢です。
その役職の後、私は幸運にも新しいスタートアップの最初の開発者として雇われました。
すべてのビルドに対して「警告なし」ポリシーを推奨し、コンパイラの警告レベルをかなりうるさく設定しました。
私たちのプラクティスは、#pragma warning-念のため、デバッグレベルでのログステートメントとともに、開発者が本当に大丈夫だと確信したコードに対してプッシュ/無効化/ポップを使用することでした。
このプラクティスは私たちにとってうまくいきました。
警告をエラーとして扱うには、oneの問題のみがあります:他のソース(たとえば、micro $ ** tライブラリ、オープンソース)からのコードを使用している場合プロジェクト)、they仕事が正しく行われず、コードをコンパイルするとtonsの警告。
Ialwaysは、警告やエラーを生成しないようにコードを記述し、不要なノイズを生成せずにコンパイルされるまでクリーンアップします。私が処理しなければならないゴミは私を驚かせ、大きなプロジェクトを構築し、コンパイルがどのファイルを処理したかだけを知らせるべきであるという警告の流れを見なければならないとき、私は驚いています。
また、ソフトウェアの実際の生涯コストは、最初にそれを書くことからではなく、主にメンテナンスから来ることを知っているので、コードを文書化しますが、それは別の話です...
一部の警告は、コード内のセマンティックエラーまたはUBの可能性を意味する場合があります。例えば。 _;
_の後のif()
、未使用の変数、ローカルでマスクされたグローバル変数、または符号付きと符号なしの比較。多くの警告は、コンパイラの静的コードアナライザー、またはコンパイル時に検出可能なISO標準の違反に関連しており、「診断が必要」です。これらの発生は1つの特定のケースでは合法かもしれませんが、ほとんどの場合設計上の問題の結果です。
いくつかのコンパイラ、例えばgccには、「エラーとしての警告」モードをアクティブにするコマンドラインオプションがあります。これは、初心者のコーダーを教育するニースのツールです。
C++コンパイラが明らかに未定義の動作をもたらすコードのコンパイルを受け入れるという事実すべては、コンパイラの重大な欠陥です。彼らがこれを修正しない理由は、そうするとおそらくいくつかの使用可能なビルドが壊れるからです。
ほとんどの警告は、ビルドの完了を妨げる致命的なエラーです。エラーを表示してビルドを行うデフォルトは間違っています。警告をエラーとして扱い、警告を残すためにそれらをオーバーライドしないと、プログラムがクラッシュしてランダムなことをすることになります。