コンパイラは、その正確性が与えられているかのように日常的に使用しますが、コンパイラもプログラムであり、潜在的にバグを含む可能性があります。私はいつもこの絶対的な堅牢性について疑問に思っていました。コンパイラ自体のバグに遭遇したことはありますか?コンパイラ自体に問題があることをどのように認識しましたか?
...そして、どのようにしてコンパイラを非常に信頼できるものにしますか?
コンパイラは、その正確性が与えられているかのように日常的に使用しますが、コンパイラもプログラムであり、潜在的にバグを含む可能性があります。私はいつもこの絶対的な堅牢性について疑問に思っていました。コンパイラ自体のバグに遭遇したことはありますか?コンパイラ自体に問題があることをどのように認識しましたか?
...そして、どのようにしてコンパイラを非常に信頼できるものにしますか?
回答:
それらは、数千または数百万の開発者による使用を通じて、時間をかけて徹底的にテストされます。
また、解決すべき問題は(非常に詳細な技術仕様によって)明確に定義されています。そして、タスクの性質は、ユニット/システムテストに容易に役立ちます。つまり、基本的には非常に特殊な形式のテキスト入力を別の種類の明確に定義された形式(ある種のバイトコードまたはマシンコード)に出力することです。そのため、テストケースの作成と検証は簡単です。
さらに、通常、バグも簡単に再現できます。正確なプラットフォームとコンパイラーのバージョン情報は別として、通常必要なのは入力コードです。コンパイラユーザー(開発者自身)が、平均的なコンピューターユーザーよりもはるかに正確で詳細なバグレポートを提供する傾向があることは言うまでもありません:-)
これまでのすべての素晴らしい答えに加えて:
あなたには「オブザーバーバイアス」があります。バグを観察しないため、バグはないと仮定します。
私はあなたのように考えていました。それから、私はコンパイラを専門的に書き始めました、そして、私にあなたに言わせてください、そこには多くのバグがあります!
人々が書く残りのコードの99.999%のようなコードを書くので、バグは見えません。通常のビジネス問題を解決する通常の開発者であるため、メソッドを呼び出してループを実行し、空想や奇妙なことは何もしません。
コンパイラのバグは簡単に分析できる簡単な通常のコードシナリオにはないため、コンパイラのバグは表示されません。バグは、あなたが書いていない奇妙なコードの分析にあります。
一方、私は反対の観察者バイアスを持っています。私は毎日狂ったコードを毎日見ているので、コンパイラーはバグでぎっしり詰まっているようです。
任意の言語の言語仕様に座って、その言語のコンパイラー実装を採用し、コンパイラーが仕様を正確に実装しているかどうかを真剣に判断しようとした場合、あいまいなコーナーケースに集中して、すぐに見つけるでしょうコンパイラのバグは頻繁に発生します。例を挙げましょう。文字通り5分前に見つけたC#コンパイラのバグです。
static void N(ref int x){}
...
N(ref 123);
コンパイラーは3つのエラーを出します。
明らかに、最初のエラーメッセージは正しく、3番目のメッセージはバグです。エラー生成アルゴリズムは、最初の引数が無効である理由を見つけようとし、それを見て、それが定数であることを確認し、「ref」としてマークされているかどうかを確認するためにソースコードに戻りません。むしろ、定数をrefとしてマークするほど愚かではないと想定し、refが欠落していると判断します。
正しい3番目のエラーメッセージが何であるかは明確ではありませんが、そうではありません。実際、2番目のエラーメッセージが正しいかどうかは不明です。オーバーロード解決が失敗する必要がありますか、または「ref 123」は正しいタイプのref引数として扱われるべきですか?正しい動作が何であるかを判断できるように、今度は考えてトリアージチームと話し合う必要があります。
このバグを見たことがないのは、おそらくrefで123を渡そうとするほど愚かなことはしないからです。また、最初のエラーメッセージが正しく、問題を診断するのに十分であるため、3番目のエラーメッセージが無意味であることに気付かないでしょう。しかし、私はコンパイラを壊そうとしているので、そのようなことをしようとしています。試した場合、バグも表示されます。
私をからかってるの?コンパイラにもバグがあり、本当にロードされます。
GCCはおそらく地球上で最も有名なオープンソースコンパイラであり、バグデータベースを参照してください:http : //gcc.gnu.org/bugzilla/buglist.cgi?product=gcc & component=c%2B%2B & resolution=-- -
GCC 3.2とGCC 3.2.3の間で、修正されたバグの数を確認してください:http : //gcc.gnu.org/gcc-3.2/changes.html
Visual C ++のような他の人については、始めたくさえありません。
コンパイラの信頼性をどのように高めますか?まあ、彼らは最初から負荷と単体テストの負荷を持っています。そして、地球全体がそれらを使用しているので、テスターが不足していません。
でも真剣に、私が信じているコンパイラ開発者は優れたプログラマーであり、彼らは間違いないわけではありませんが、彼らは非常にパンチが効いています。
私は1日で2つまたは3つに遭遇しました。1つを検出する唯一の実際の方法は、アセンブリコードを調べることです。
コンパイラは他のポスターが指摘した理由で非常に信頼性が高いですが、コンパイラの信頼性は多くの場合自己満足の評価であると思います。プログラマは、コンパイラを標準と見なす傾向があります。何かがおかしくなった場合、あなたは自分の過ちを想定し(99.999%の確率で)、コードを変更してコンパイラーの問題を回避します。たとえば、高い最適化設定でコードがクラッシュするのは間違いなくコンパイラのバグですが、ほとんどの人はそれを少し低く設定して、バグを報告せずに先に進みます。
コンパイラには、正確さをもたらすいくつかのプロパティがあります。
私たちは日常的にコンパイラを使用しています
...そして、どのようにしてコンパイラを非常に信頼できるものにしますか?
彼らはしません。します。ので、誰もが彼らのすべての時間を使用して、バグがすぐに発見されました。
それは数字ゲームです。コンパイラは非常に広範に使用されるため、バグは誰かによって引き起こされる可能性が非常に高いですが、ユーザーが非常に多いため、誰かが具体的にあなたになることはほとんどありません。
したがって、それはあなたの視点に依存します:すべてのユーザーにわたって、コンパイラはバグが多いです。しかし、他の誰かがあなたがする前に同様のコードをコンパイルした可能性が非常に高いので、彼らがバグだった場合、あなたではなく彼らにぶつかりますので、あなたの個々の観点からは、バグは決してない。
もちろん、それに加えて、他のすべての答えをここに追加できます。コンパイラーはよく研究され、よく理解されています。書くのは難しいという神話があります。つまり、非常に賢く、非常に優秀なプログラマーだけが実際にそれを書き込もうとします。通常、テストは簡単で、ストレステストやファズテストは簡単です。コンパイラーのユーザーは、それ自体が熟練したプログラマーである傾向があり、高品質のバグ報告につながります。そして、他の方法:コンパイラライターは、自分のコンパイラのユーザーである傾向があります。
コンパイラのバグに頻繁に遭遇しました。
テスターが少ない暗いコーナーで見つけることができます。たとえば、GCCのバグを見つけるには、次のことを試してください。
いくつかの理由:
通常、-O0は非常に優れています。実際、コンパイラのバグが疑われる場合、-O0と使用しようとしているレベルを比較します。最適化レベルが高いほど、リスクが高くなります。意図的にそうであるものもあり、ドキュメントでそのようにラベル付けされています。私は非常に多くの人(私の時間の間に少なくとも100人)に遭遇しましたが、それらは最近非常に稀になりつつあります。それにもかかわらず、優れたスペックマーク数(またはマーケティングにとって重要な他のベンチマーク)を追求する上で、限界を押し上げる誘惑は大きい。数年前に、ベンダーが(名前を付けずに)特別に明確にラベル付けされたコンパイルオプションではなく、括弧のデフォルトに違反することにした問題がありました。
漂遊メモリ参照などと比較してコンパイラエラーを診断するのは難しい場合があります。異なるオプションで再コンパイルすると、メモリ内のデータオブジェクトの相対的な位置が単純にスクランブルされる可能性があるため、ソースコードのハイゼンバグであるかバギーであるかはわかりませんコンパイラ。また、多くの最適化では、演算の順序が合法的に変更されたり、代数の代数的単純化が行われたりします。これらは、浮動小数点の丸めとアンダー/オーバーフローに関して異なるプロパティを持ちます。これらの効果を実際のバグから解きほぐすのは困難です。この理由から、ハードコアの浮動小数点計算は困難です。これは、バグや数値の感度を解くのが簡単ではないことが多いためです。
コンパイラのバグはそれほど珍しいことではありません。最も一般的なケースは、コンパイラが受け入れられるべきコードのエラーを報告するか、コンパイラが拒否されるべきコードを受け入れることです。
そう、昨日ASP.NETコンパイラのバグに遭遇しました。
ビューで厳密に型指定されたモデルを使用する場合、テンプレートに含めることができるパラメーターの数には制限があります。明らかに、4つ以上のテンプレートパラメータを取ることはできません。そのため、以下の両方の例では、コンパイラが処理するには多すぎます。
ViewUserControl<System.Tuple<type1, type2, type3, type4, type5>>
そのままコンパイルしませんtype5
が、削除した場合はコンパイルします。
ViewUserControl<System.Tuple<MyModel, System.Func<type1, type2, type3, type4>>>
type4
が削除されるとコンパイルされます。
これにSystem.Tuple
は多くのオーバーロードがあり、最大16個のパラメーターを使用できることに注意してください(私が知っているのはクレイジーです)。
コンパイラ自体のバグに遭遇したことはありますか?コンパイラ自体に問題があることをどのように認識しましたか?
うん!
最も記憶に残る2つは、私が出会った最初の2つでした。それらは両方とも1985-7年頃の680x0 Mac用のLightspeed Cコンパイラに含まれていました。
1つ目は、状況によっては、整数のポストインクリメント演算子が何もしないことです。つまり、特定のコードでは、「i ++」は単に「i」に対して何もしませんでした。分解を見るまで、私は髪を引っ張っていました。それから、私はちょうど別の方法で増分を行い、バグレポートを提出しました。
2つ目はもう少し複雑で、実際には不適切な「機能」でしたが、問題が発生しました。初期のMacには、低レベルのディスク操作を行うための複雑なシステムがありました。何らかの理由で-おそらく小さな実行可能ファイルの作成に関係する-コンパイラがオブジェクトコード内でディスク操作命令をインプレースで生成するのではなく、Lightspeedコンパイラが実行時にディスク操作を生成する内部関数を呼び出しますスタック上の命令とそこにジャンプしました。
これは68000 CPUでうまく機能しましたが、68020 CPUで同じコードを実行すると、しばしば奇妙なことをします。68020の新しい機能は、プリミティブ命令の256バイト命令キャッシュであることが判明しました。これはCPUキャッシュを使用した初期の段階であり、キャッシュが「ダーティ」であり、補充が必要であるという概念はありませんでした。MotorolaのCPU設計者は、自己修正コードについて考えていなかったと思います。したがって、実行シーケンスで2つのディスク操作を十分に近づけて実行し、Lightspeedランタイムがスタック上の同じ場所に実際の命令を構築した場合、CPUは命令キャッシュがヒットしたと誤って判断し、最初のディスク操作を2回実行します。
繰り返しになりますが、それを理解するには、逆アセンブラーを掘り下げ、低レベルデバッガーで多くのシングルステップを実行する必要がありました。私の回避策は、すべてのディスク操作の前に256個の「NOP」命令を実行する関数の呼び出しを追加し、命令キャッシュをフラッディング(つまりクリア)することでした。
それから25年以上にわたり、時間の経過とともにコンパイラのバグが少なくなっています。その理由はいくつかあると思います。
5.5年前にTurbo Pascalで明白なエラーを発見しました。コンパイラの前のバージョン(5.0)と次のバージョン(6.0)のどちらにもエラーがありません。そして、テストは簡単なはずでしたが、これはまったくコーナーケースではなかったためです(一般的に使用されていない呼び出しだけです)。
一般に、確かに商用のコンパイラビルダー(趣味のプロジェクトではなく)には、非常に広範なQAとテスト手順が用意されています。彼らは彼らのコンパイラが彼らの主力プロジェクトであり、欠陥が彼らにとって非常に悪く見えることを知っています。ソフトウェア開発者は容赦ない束であり、ツールのサプライヤは、サプライヤからの修正を待つのではなく、代替品を探しに行く可能性があります。例。他の多くの業界ではそうではないので、深刻なバグの結果としてのコンパイラーメーカーへの潜在的な損失は、ビデオ編集ソフトウェアのメーカーよりもはるかに大きいです。
コンパイラのバグは発生しますが、奇妙なコーナーでそれらを見つける傾向があります...
1990年代にDigital Equipment CorporationのVAX VMS Cコンパイラに奇妙なバグがありました
(当時のファッションのように、私はベルトにタマネギを着ていました)
forループの前にある無関係なセミコロンは、forループの本体としてコンパイルされます。
f(){...}
;
g(){...}
void test(){
int i;
for ( i=0; i < 10; i++){
puts("hello");
}
}
問題のコンパイラでは、ループは1回だけ実行されます。
見る
f(){...}
g(){...}
void test(){
int i;
for ( i=0; i < 10; i++) ; /* empty statement for fun */
{
puts("hello");
}
}
それには時間がかかりました。
作業経験のある学生に与えていた古いバージョンのPIC Cコンパイラは、高優先度割り込みを正しく使用するコードを生成できませんでした。2〜3年待ってからアップグレードする必要がありました。
MSVC 6コンパイラにはリンカに気の利いたバグがあり、セグメンテーションエラーが発生し、理由もなく時々死にます。通常、クリーンビルドで修正されました(ただし、常にため息があるわけではありません)。
私はいくつかのコンパイラのバグを見てきましたが、私自身(特にF#で)をいくつか報告しました。
とは言っても、コンパイラを書く人は一般に、コードの数学的な意味を本当に意識させるコンピュータサイエンスの厳密な概念に非常に満足しているため、コンパイラのバグはまれだと思います。
それらのほとんどは、ラムダ計算、形式的検証、表示的意味論などのようなものにおそらく非常に精通している-私のような平均的なプログラマーがかろうじて理解できるもの。
また、通常、コンパイラには入力から出力へのかなり簡単なマッピングがあるため、プログラミング言語のデバッグは、おそらくブログエンジンなどのデバッグよりもはるかに簡単です。
C#コンパイラでバグを見つけたのはそれほど前ではなく、Eric Lippert(C#デザインチームのメンバー)がどのようにバグがここにあったのかを理解できたことがわかります。
すでに与えられた答えに加えて、私はさらにいくつかのことを追加したいと思います。多くの場合、コンパイラの設計者は非常に優秀なプログラマです。コンパイラは非常に重要です。ほとんどのプログラミングはコンパイラを使用して行われるため、コンパイラは高品質であることが不可欠です。したがって、コンパイラーを作成する企業にとっては、最高の人材を配置すること(または、少なくとも非常に優れたもの:コンパイラー設計が気に入らないかもしれません)にとって最大の利益になります。Microsoftは、CおよびC ++コンパイラが適切に動作することを非常に望んでいます。
また、本当に複雑なコンパイラを構築している場合、一緒にハックすることはできません。コンパイラの背後にあるロジックは非常に複雑であり、形式化が容易です。したがって、これらのプログラムは多くの場合、非常に「堅牢」で一般的な方法で構築され、バグが少なくなる傾向があります。