HN 日本語サマリー

← 一覧へ戻る
プログラミング

C++26: 自明な無限ループはもはや未定義の動作ではなくなる

C++26: Trivial infinite loops are no longer undefined behaviour (sandordargo.com)

38 pointsby ibobev73 コメント

要約

C++26では、`while(true);`のような自明な無限ループが未定義の動作ではなくなり、仕様として定義されるようになります。これにより、コンパイラがこれらのループを最適化で削除してしまう問題が解消され、組み込みシステムやセキュリティ分野での予期せぬ動作を防ぐことができます。この変更は、ループ本体が空で、条件式が常に真である定数式である場合に適用され、代わりに`std::this_thread::yield()`が呼び出されるようになります。

全文翻訳

Sandor Dargo's Blog C++, ソフトウェア開発、書籍について HOME TAGS ARCHIVES BOOKS SPEAKING DAILY C++ WORKSHOPS HI... SUBSCRIBE Blog 2026 09 16 C++26: 自明な無限ループはもはや未定義の動作ではなくなる Post Cancel C++26: 自明な無限ループはもはや未定義の動作ではなくなる Sandor Dargo Sep 16 2026-09-16T00:00:00+02:00 5 min まず、質問から始めましょう!このプログラムは定義されていますか? 1 2 3 4 int main() { while (true); } もし「はい」と答えたなら、あなたは間違っています。少なくともC++26より前はそうでした。副作用のない `while (true);` ループは、かつて未定義の動作でした。コンパイラはそれを終了すると仮定することが自由であり、一部のコンパイラ(特にClang)はそれを完全に最適化で削除し、驚くべき結果をもたらしました。 1 2 3 4 5 6 7 8 9 10 11 // https://godbolt.org/z/WYMxxeW1T #include <iostream> int main() { while (true); } void unreachable() { std::cout << "Hello world!" << std::endl; } Clangでは、これは「Hello world!」と表示されます。コンパイラは無限ループを削除し、main関数は処理を続行し、リンカによって配置された `unreachable()` 関数が実行されます。これはコンパイラのバグではなく、単に未定義の動作(UB)であり、鼻の悪魔(nasal demons)よりもまだ良いです。 最近、C++26が未定義の動作をどのように削減するかについて書きました。初期化されていない読み取りのエラー動作や、不完全型の削除を不正な形式にするなどの変更が含まれています。この件については完全に忘れていました。CppConでのC++26の新機能に関する今後の講演の準備中に気づいたのです。それでは、今ここで説明します。 C++26は、P2809R3によってこれを修正します。自明な無限ループは、もはや定義されない動作ではなくなります。言及された提案は、不具合報告としても受理されたため、実装は以前のC++モードにもこの修正を適用できる可能性があります。そのため、最近のコンパイラではC++20モードであっても、古い動作を再現できないかもしれません。 どうしてこうなったのか? この話は、C++11でスレッドサポートと同時に導入されたフォワードプログレス保証(forward progress guarantee)から始まります。標準では([intro.progress])、実装は任意の Сレッドが最終的に以下のいずれかを行うと仮定してもよいと述べています:終了する、ライブラリI/O関数を呼び出す、volatile glvalueにアクセスする、または同期操作またはアトミック操作を実行する。 `while (true);` ループはこれらのいずれも行いません。C++26より前のフォワードプログレス規則の下では、このようなループに永遠に留まる実行は未定義の動作でした。したがって、オプティマイザは実行が決してそこでスタックしないと仮定することができ、これによりループを削除し、パスを到達不能とマークする変換が可能になりました。 面白いのは、C言語はこの点を正しく扱っていたことです。C++11とC11は両方ともフォワードプログレス規則を導入しましたが、C言語にはさらに1つの規則が含まれていました:制御式が定数式であるループは、終了すると仮定してはならないのです。したがって、`while (1);` はC11以降で定義されています。 C++はこの追加の規則を採用しませんでした。その結果、説明した不必要な分岐が生じましたが、繰り返します:`while (1);` はC言語では定義されていましたが、C++では未定義の動作でした。 しかし、そもそもなぜ誰も `while (true);` を書くのでしょうか? 私が発見したところによると、これは組み込みシステムやカーネルコードで、エラー発生時の停止パターンとして一般的です。致命的なエラーが発生し、終了できるオペレーティングシステムがない場合、単に停止します。 1 2 3 4 5 if (hardware_init_failed()) { log_error("fatal: hardware init failed"); while (true); // halt — there's nothing left to do } これはベアメタル(bare metal)で一般的なパターンであるだけでなく、C++では未定義の動作でした。その結果は理論的なものではありません。オプティマイザがループを削除すると、実行は、リンカがその後に配置したコードにフォールスルーします。この記事の冒頭の「Hello world!」の例がそれを実証しています。組み込みシステムでは、これは致命的なエラーハンドラが実際にはデバイスを停止させないことを意味します。ハードウェアは、破損した状態で実行を続け、後続の命令を都合よく実行します。セキュリティが重要なコードでは、これは実際の脆弱性です。 C++26で何が変わるか しかし、C++26は単にC言語の規則をコピーするわけではありません。そのアプローチは検討されましたが却下されました。C言語は、より広範なループ(一般に、制御式が定数式であるループ)を保護しますが、これは有用な最適化を妨げる可能性があります。代わりに、P2809R3は意図的に狭いカテゴリを定義しています:自明な無限ループです。これは2つの条件によって定義されます。 ループは、自明な空の反復ステートメントである必要があります。つまり、その本体は文字通り空(`;` または `{}`)である必要があります。意味のない式ステートメント(例:「a string」;)のような、空でないステートメントは、それを無効にします。 制御式は、真と評価される定数式である必要があります。条件のないforループの場合、真は暗黙的です。 両方の条件が満たされると、ループ本体は `std::this_thread::yield()` の呼び出しに置き換えられます。これにより、ループの実行に、以前は欠けていたフォワードプログレスセマンティクスが与えられます。 以下は、該当するものとしなかったものです。 Code Trivial infinite loop? while (true); Yes for (;;); Yes do {} while (true); Yes constexpr bool go = true; while (go); Yes — go is a constant expression while (true) { "a string"; } No — body contains a statement while (true) if (done) break; No — body is not empty while (true) if constexpr (false) break; No — doesn’t match the syntax of a trivially empty iteration statement bool done = false; while (!done); No — not a constant expression この変更は、フォワードプログレス保証自体も更新します。スレッドは、最終的に行うことが期待されることの1つとして、「自明な無限ループの実行を継続する」ことができるようになりました。したがって、オプティマイザはもはや自明な無限ループを未定義の動作として扱い、実行がそれを通過すると仮定することはできません。 フリースタンディング(freestanding)環境に関する注意 フリースタンディング実装では、`std::this_thread::yield()` への置き換えが発生するかどうかは実装定義です。これはベアメタルシステムにとって重要です。意図的に停止させるループを協調的なyieldに変換すると、プログラマが意図しなかった動作が発生する可能性があります。 結論 `while (true);` が未定義の動作であったことは、それを聞いた誰もが驚くC++の事実の1つでした。それはC言語からの不必要な逸脱であり、実際の組み込みコードを壊し、コンパイラはそれを実際に悪用していました。C++26はこれを修正します。自明な無限ループは、もはや未定義の動作ではなくなり、コンパイラはそれを最適化で削除できなくなりました。 さらに深くつながる この記事が気に入ったら、いいねボタンを押して、私のニュースレターを購読し、Twitterでつながりましょう! C++ quant/tradingの面接を準備しているなら、GetCrackedをチェックしてください dev cpp cpp26 undefinedbehaviour この投稿は、著者のCC BY 4.0ライセンスの下で提供されています。 共有 最近の更新 C++26: std::hive C++26: ライブラリ機能の非推奨または削除 C++26: std::polymorphic C++26: std::indirect C++26: 文字列リテラルの整理 トレンドタグ cpp books watercooler career tutorial cpp23 cpp26 stl algorithms self-improvement コンテンツ さらに読む 2025年2月26日 2025-02-26T00:00:00+01:00 C++26: 字句解析とプリプロセスにおけるUBはもうない C++を使ったことがあるなら、間違いなく未定義の動作(UB)に直面したことがあるでしょう。実装者に余分な自由を与える一方で、システムに大混乱を引き起こす可能性があるため、開発者には恐れられています。それは... 2026年7月29日 2026-07-29T00:00:00+02:00 C++26: 未定義の動作の削減 最近、安全性はC++の中心となっています。委員会での会議、カンファレンストーク、廊下での議論であれ、このトピックは常に話題に上ります。C++26はこの分野で大きな一歩を踏み出しました。 ... 2024年12月11日 2024-12-11T00:00:00+01:00 C++26を探求し始めましょう 過去2年間、私たちはC++23を探求するのに多くの時間を費やし、ほぼ40件のブログ記事を公開しました。言語やライブラリのすべての新機能を取り上げたとは言いませんが、そのほとんどをカバーしました。 ... C++26: モジュールの改善 - Disqusによるコメント。 C++開発者向け より良いコード。より良いキャリア。モダンC++と成長に必要なものについてのメールを週に1回。まだ自分も探求中の人間が書いています。 購読 スパムは送りません。いつでも購読解除できます。 Kitで構築 トレンドタグ cpp books watercooler career tutorial cpp23 cpp26 stl algorithms self improvement