このプロセスはデバッグと呼ばれます。 毎日、すべてのプログラマーは、コードを書くときに、コードにエラーを作成するという事実に直面しています。 そして、彼は自分のプログラムが機能していないことに気づくとすぐに、彼自身が作成した問題を探す必要があります。
このような問題を解決するために、コンピューター業界では、プログラムが正しく機能していることを確認するのに役立つ膨大な数のツールを作成しています。 プログラマーは、 継続的な統合方法、 単体テスト 、 アサーション 、 デバッガーなどを使用してエラーを検索します。 しかし、エラーはまだ残っており、人間の思考の助けを借りて排除する必要があります。
Cなどの一部のプログラミング言語は、このような種類のエラーの影響を非常に受けやすく、ランダムに表示されたり消えたりします。エラーが表示される理由を理解するとすぐにエラーが消えます。 こうしたエラーは、多くの場合、 ハイゼンバッグと呼ばれます。なぜなら、それらを探し始めるとすぐに消えてしまうからです。
このようなエラーは、プログラミング言語で発生する可能性があります。特に、マルチタスクコードを作成する場合は、時間のわずかな遅延が競合状態を引き起こす可能性があります 。 しかし、Cには別の問題があります-メモリリーク。
ただし、エラーの発生を回避するために、問題を見つけるための重要な手順は常に次のとおりです。
- エラーが完全に再現される最小のパターンを見つけます。 heisenbagsではこれは難しい場合がありますが、エラーが再現されるテストのわずかな割合でさえ重要です。
- テストプロセスを自動化します。 テストを何度も実行できる場合は、はるかに優れています。 エラーが修正されたときにプログラムの一部にすることもできます。これにより、エラーが再び表示されることはありません。
- 原因が見つかるまで原因を検索します。 エラーの真の原因が見つかるまで、修正したと断言することはできません。 Heisenbagsを使用すると、検索プロセスでの操作後に突然エラーが消えた後、エラーを修正したと信じて非常に混乱する可能性があります。
- 原因を修正し、手順2を使用して確認します。
最近、Hacker Newsに記事が掲載されました-Cにheisenbagがある場合、問題はコンパイラオプティマイザーにあります。 これは非常に間違った判断です。
使用するコンパイラはおそらく何千人もの人々によって使用されますが、プログラムはほとんどの場合あなただけによって使用されます。 コンパイラまたはプログラムが最も安定して動作すると思いますか?
実際、プログラマーの経験不足の兆候は、エラーを探すときに最初に行うことは、他の誰かを非難することであるという事実です。 使用しているコンピューター、オペレーティングシステム、ライブラリを非難するのは非常に魅力的です。 ただし、真のプログラマーとは、自分の「I」を制御し、エラーが彼の可能性が高いことを理解できるプログラマーです。
もちろん、他のプログラマーのコードにはエラーがあります。 ライブラリが動作しないこと、オペレーティングシステムが奇妙なことをすること、そしてコンパイラが奇妙なコードを生成することは疑いありません。 しかし、多くの場合、これはあなたの間違いであり、これはエラーがあまりにも奇妙に見える場合でも当てはまります。
デバッグプロセスでは、多くの場合、自分のコードに頭を突っ込み、コードでは起こりえない最も不可能なことを何度も繰り返します。 しかし、ある時点で、不可能が可能になり、間違いを見つけます。
上記の記事には、完全ではない1つの例があります。
「オプティマイザーをオフにして、プログラムを再度チェックしてください。 動作する場合、問題はオプティマイザーにあります。 最適化レベルをいじって、エラーの再生が始まるまでレベルを上げてください。
最適化レベルを変更する際に知っているのは、エラーが発生したかどうかに関係なくレベルが変わることだけです。 これは、オプティマイザーが正常に動作していないことを通知するものではありません。 エラーの理由が見つかりませんでした。
オプティマイザーはコードを操作して作業を高速化するため、最適化のレベルに応じて、ハイゼンバッグが表示されたり消えたりする可能性があります。 これは、オプティマイザーが正常に動作していないことを意味しません。 これは、おそらくあなたの間違いです。
以下は、コンパイラの最適化レベルが変更されたときに表示されるエラーを含むCプログラムの具体例で、プログラムの奇妙な動作を示しています。
#include <stdlib.h>
int a()
{
int ar[16];
ar[20] = (getpid() % 19 == 0);
}
int main(int argc, char * argv[])
{
int rc[16];
rc[0] = 0;
a();
return rc[0];
}
次のMakefileを使用して、Mac OS Xでgccを使用してこのプログラムをコンパイルします(odd.cファイルにコードを保存しました)。
CC=gcc
CFLAGS=
odd: odd.o
次に、プログラムを20回実行して結果を表示するスクリプトの例を示します。
#!/bin/bash
for i in {0..20}
do
./odd ; echo -n "$? "
done
echo
このスクリプトを実行すると、rc [0]はゼロ以外の値を取得しないため、ゼロの文字列が期待されます。 ただし、プログラムの例は次のとおりです。
$ ./test
0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
あなたが経験豊富なCプログラマである場合、私がユニットをどのように表示したか、なぜそれが異なる場所に表示されるかがわかります。 しかし、今度はprintfを使用してプログラムをデバッグしてみましょう。
[...]
rc[0] = 0;
printf( "[%d]", rc[0] );
a();
[...]
これで、プログラムを実行すると、エラーが消えます。
$ ./test
[0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0
[0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0 [0]0
奇妙に見えるので、printfを別の場所に移動します。
[...]
rc[0] = 0;
a();
printf( "[%d]", rc[0] );
[...]
また、エラーが消えるという奇妙な結果が得られます。 そして、オプティマイザーをオフにしても同じことが起こり、printfがなくてもエラーは表示されません。
$ make CFLAGS=-O3
gcc -O3 -c -o odd.o odd.c
gcc odd.o -o odd
$ ./test
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
これはすべて、f a()が16個の整数要素にメモリを割り当てるために発生します。 そして、配列の終わりの直後に、プロセスPIDが19で除算されるかどうかに応じて、1または0を書き込みます。 最終的に、スタック上の位置により、rc [0]に書き込まれます。
printfを追加するか、最適化レベルを変更すると、コードの場所が変更され、rc [0]の誤った呼び出しが排除されます。 しかし、注意してください、エラーは消えませんでした。 ユニットは単に別のメモリ位置に書き込みました。
なぜなら Cはこのタイプのエラーの影響を非常に受けやすいため、適切なツールを使用してこのような問題をチェックすることが重要です。 たとえば、静的コードアナライザーのスプリントとメモリアナライザーのvalgrindは 、多くの厄介なエラーを排除するのに役立ちます。 そして、最大レベルのエラーでアプリケーションを開発し、それらをすべて排除する必要があります。
必要なことを行う場合にのみ、他の人のコードを疑うことができます。 ただし、これを開始した場合でも、すべての手順を再度確認して、エラーの真の原因を特定してください。 残念ながら、ほとんどの場合、間違いのほとんどはあなたのものです。