[解決済み] 動作が未定義のブランチは到達不可能とみなし、デッドコードとして最適化することができますか?
質問
次の文章を考えてみましょう。
*((char*)NULL) = 0; //undefined behavior
これは明らかに未定義の動作を呼び出しています。あるプログラムの中にそのような文があるということは、プログラム全体が未定義であるということでしょうか、それとも制御フローがこの文にぶつかると動作が未定義になるだけでしょうか?
次のプログラムは、ユーザが番号を入力しない場合、よく定義されたものになりますか。
3
?
while (true) {
int num = ReadNumberFromConsole();
if (num == 3)
*((char*)NULL) = 0; //undefined behavior
}
それとも、ユーザーが何を入力しても、全く未定義の動作なのでしょうか?
また、コンパイラは未定義の動作が実行時に決して実行されないと仮定することができるでしょうか。そうすれば、時間を逆算して推論することが可能になります。
int num = ReadNumberFromConsole();
if (num == 3) {
PrintToConsole(num);
*((char*)NULL) = 0; //undefined behavior
}
ここで、コンパイラは、もし
num == 3
の場合、常に未定義の動作を呼び出すことになる。したがって、このケースは不可能に違いなく、数値を表示する必要はない。全体の
if
ステートメント全体が最適化されるかもしれません。このような後方推論は、標準に従って許可されているのでしょうか?
どのように解決するのですか?
あるプログラム中にこのような文があると、プログラム全体が不定になるのでしょうか? プログラム全体が未定義であるということなのでしょうか? 制御フローがこのステートメントにぶつかると、動作が不定になるだけなのでしょうか?
どちらでもありません。最初の条件は強すぎ、2番目は弱すぎです。
オブジェクトのアクセスは時々順番に行われますが、標準は時間以外のプログラムの動作を記述します。Danvilはすでに引用しています。
もしそのような実行が未定義の操作を含んでいるならば、この 国際規格は、その入力でそのプログラムを実行する実装に、何の要件も課しません。 そのプログラムをその入力で実行することに対し、(最初の未定義の操作の前の操作に関しても 最初の未定義の操作の前の操作に関しても)
と解釈することができる。
<ブロッククオートプログラムの実行が未定義の動作をもたらすのであれば、プログラム全体が未定義の動作を持つことになります。 未定義の振る舞いをすることになります。
つまり、UBで到達不能な文は、プログラムにUBを与えない。入力の値のために)決して到達されない到達可能なステートメントは、プログラムにUBを与えません。そういうわけで、最初の条件は強すぎるのです。
さて、コンパイラは一般的に何がUBを持つかを知ることができません。そのため、オプティマイザーが潜在的な UB を持つステートメントを再順序付けできるようにするには、UB が "reach back in time" と先行するシーケンス ポイントの前に間違って行くことを許可する必要があります (C++11 用語では、UB が UB 事前にシーケンスされたものに影響を与えるためです)。したがって、あなたの 2 番目の条件は弱すぎです。
この主な例は、オプティマイザーが厳密なエイリアシングに依存する場合です。厳密なエイリアシング規則の要点は、問題のポインターが同じメモリをエイリアシングしている可能性がある場合、コンパイラーが有効に再順序付けできない操作を再順序付けできるようにすることです。つまり、不正にエイリアシングされたポインタを使用し、UBが発生した場合、UB文の"before"に簡単に影響を与えることができるのです。抽象的なマシンに関する限り、UBステートメントはまだ実行されていない。実際のオブジェクトコードに関しては、一部または全部が実行されています。しかし、この規格は、オプティマイザがステートメントを並べ替えることの意味や、それがUBに与える影響について、詳細に触れようとはしていない。それはただ、好きなだけすぐに間違うことができるライセンスを実装に与えるだけです。
あなたはこれを、「UBはタイムマシンを持っている」と考えることができます。
具体的に例に答えると
- 3が読み込まれた場合のみ挙動が不定になります。
-
コンパイラーは、基本ブロックが未定義であることが確実な操作を含んでいる場合、コードをデッドとして排除することができますし、実際そうしています。基本ブロックではないが、すべての分岐が UB につながるようなケースでも許可されます (そして、私はそうしていると推測しています)。この例は
PrintToConsole(3)
が確実に戻ることが何らかの形で知られていない限り、この例は候補にはなりません。それは例外を投げるか何かかもしれません。
2番目の例と似たような例として、gccのオプションがあります。
-fdelete-null-pointer-checks
で、これは次のようなコードを取ることができます (この特定の例をチェックしたわけではありません。一般的な考えを示すものだと考えてください)。
void foo(int *p) {
if (p) *p = 3;
std::cout << *p << '\n';
}
と変更します。
*p = 3;
std::cout << "3\n";
なぜかというと なぜならもし
p
が NULL である場合、コードはどのみち UB を持っているので、コンパイラは NULL でないと仮定し、それに応じて最適化することができるからです。linux カーネルはこれにつまづいた (
https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1897
を参照するモードで動作しているためです。
は
UB であるべきではなく、カーネルが処理できる定義されたハードウェア例外が発生することが期待されています。最適化が有効な場合、gcc では
-fno-delete-null-pointer-checks
を使用する必要があります。
P.S. 「未定義の動作はいつ起こるのか」という質問に対する実際の答えは、「その日に出発する予定の 10 分前」です。
関連
-
[解決済み】標準ライブラリにstd::endlに相当するタブはあるか?
-
[解決済み] 数値定数の前にunqualified-idを付けて、数値を定義することを期待する。
-
[解決済み] 警告:暗黙の定数変換でのオーバーフロー
-
[解決済み] 未定義の動作とシーケンスポイント
-
[解決済み] C言語とC++の両方で有効なコードを、それぞれの言語でコンパイルすると、異なる動作になることがありますか?
-
[解決済み] 未定義、未指定、および実装で定義された動作
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】なぜこれらのコンストラクトはプリインクリメントとポストインクリメントを使用して未定義の動作をしているのでしょうか?
-
[解決済み】f(i = -1, i = -1)の挙動が未定義なのはなぜ?
-
[解決済み] NULLインスタンスに対してメンバ関数を呼び出すと、どのような場合に未定義の動作となるのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++エラー。アーキテクチャ x86_64 に対して未定義のシンボル
-
[解決済み】C++ 非推奨の文字列定数から「char*」への変換について
-
[解決済み】C++でランダムな2倍数を生成する
-
[解決済み】関数名の前に期待されるイニシャライザー
-
[解決済み】「corrupted size vs. prev_size」glibc エラーを理解する。
-
[解決済み】テンプレートの引数1が無効です(Code::Blocks Win Vista) - テンプレートは使いません。
-
[解決済み】デバッグアサーションに失敗しました。C++のベクトル添え字が範囲外
-
[解決済み】クラスのコンストラクタへの未定義参照、.cppファイルの修正も含む
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み】エラー。引数リストに一致するコンストラクタのインスタンスがない