コンパイラの最適化により、バグや望ましくない動作が発生する可能性があります。そのため、それらをオフにすることができます。
1 つの例: コンパイラーは、メモリー位置への読み取り/書き込みアクセスを最適化し、重複読み取りまたは重複書き込みを排除したり、特定の操作の順序を変更したりすることができます。問題のメモリ ロケーションが単一のスレッドによってのみ使用され、実際にメモリである場合は、問題ない可能性があります。しかし、メモリの場所がハードウェア デバイスの IO レジスタである場合、書き込みの並べ替えや削除は完全に間違っている可能性があります。この状況では、通常、コンパイラーがコードを「最適化」する可能性があることを認識してコードを作成する必要があるため、単純なアプローチが機能しないことを認識しています。
アップデート:Adam Robinson がコメントで指摘したように、私が上で説明したシナリオは、オプティマイザーのエラーというよりはプログラミングのエラーです。しかし、私が説明しようとしていたポイントは、他の点では正しく動作するいくつかの最適化と組み合わされたいくつかのプログラムは、それらが組み合わされたときにプログラムにバグを導入する可能性があるということです. 場合によっては、言語仕様に「この種の最適化が行われる可能性があり、プログラムが失敗する可能性があるため、この方法で行う必要があります」と記載されている場合があります。この場合、それはコードのバグです。ただし、コンパイラが (通常はオプションの) 最適化機能を持っている場合があります。これは、コンパイラがコードを最適化しようと試みすぎたり、最適化が不適切であることを検出できないために、正しくないコードを生成する可能性があるためです。
別の例: Linux カーネルには、 NULL である可能性のあるポインターが null であるかどうかのテストの前に逆参照されるというバグがありました。ただし、場合によっては、メモリをアドレス 0 にマップして、逆参照を成功させることができました。コンパイラは、ポインターが逆参照されたことに気付くと、それが NULL であってはならないと想定し、後で NULL テストとその分岐内のすべてのコードを削除しました。これにより、コードにセキュリティの脆弱性が導入されましたこれは、攻撃者が提供したデータを含む無効なポインターを関数が使用するためです。ポインターが合法的に null であり、メモリがアドレス 0 にマップされていない場合、カーネルは以前と同様に OOPS を返します。そのため、最適化前のコードには 1 つのバグが含まれていました。2 つ含まれていた後、そのうちの 1 つがローカルの root エクスプロイトを可能にしました。
CERT には、 Robert C. Seacord による「Dangerous Optimizations and the Loss of Causality」というプレゼンテーションがあり、プログラムにバグを導入 (または公開) する多くの最適化がリストされています。「ハードウェアが行うことを行う」から「考えられるすべての未定義の動作をトラップする」、「許可されていないことを行う」まで、可能なさまざまな種類の最適化について説明します。
積極的に最適化するコンパイラが手に入れるまでは、まったく問題のないコードの例をいくつか示します。
オーバーフローのチェック
// fails because the overflow test gets removed
if (ptr + len < ptr || ptr + len > max) return EINVAL;
オーバーフロー算術をまったく使用する:
// The compiler optimizes this to an infinite loop
for (i = 1; i > 0; i += i) ++j;
機密情報のメモリをクリアする:
// the compiler can remove these "useless writes"
memset(password_buffer, 0, sizeof(password_buffer));
ここでの問題は、何十年もの間、コンパイラが最適化にあまり積極的でなかったことです。そのため、C プログラマーの世代は、固定サイズの 2 の補数の加算やそれがどのようにオーバーフローするかなどを学び、理解しています。その後、コンパイラ開発者によって C 言語の標準が修正され、ハードウェアが変わらないにもかかわらず、微妙なルールが変更されます。C 言語仕様は開発者とコンパイラの間の契約ですが、契約の条件は時間の経過とともに変更される可能性があり、すべての人がすべての詳細を理解しているわけではなく、詳細が理にかなっているとさえ同意しているわけではありません。
これが、ほとんどのコンパイラが最適化をオフ (またはオン) にするフラグを提供する理由です。あなたのプログラムは、整数がオーバーフローする可能性があることを理解して書かれていますか? 次に、バグが発生する可能性があるため、オーバーフローの最適化をオフにする必要があります。あなたのプログラムは、ポインターのエイリアシングを厳密に回避していますか? 次に、ポインターがエイリアス化されないことを前提とした最適化をオンにできます。あなたのプログラムは、情報の漏洩を避けるためにメモリをクリアしようとしますか? ああ、その場合は運が悪い: デッド コード リムーバルをオフにするか、コンパイラが「デッド」コードを削除して何らかの作業を行うことを前もって知る必要がある-それについて。