C++26、無限ループを未定義動作から救う
C++26: Trivial infinite loops are no longer undefined behaviour
C++ではwhile(true);のような無限ループが未定義動作とされ、Clangはループを削除してリンカが配置した関数を実行することがあった。C++26のP2809R3は、本体が空で制御式が定数trueの「自明な無限ループ」を明確に定義し、std::this_thread::yield()呼び出しに置き換える。組み込みやカーネルで使われる停止パターンの安全性が向上する。
C++26はこれをP2809R3で修正する。自明な無限ループはもはや明確に定義される。
HNでの議論
272- wahern
> 両方の条件が満たされると、ループ本体はstd::this_thread::yield()の呼び出しに置き換えられます。これにより、ループの実行に以前は欠けていたforward-progressセマンティクスが与えられます。
これこそ、Linusや他の多くの人々がC++に対して嫌悪感を示す、隠れたコードの欠点の典型例だ。コンストラクタとデストラクタに関しては、ある程度避けられず、それほどランダムではないが、Rustは少なくともdropの場合において、非ローカルコードの影響範囲を限定するのが上手い。
もしC11のルールを採用したくなかったのなら、C++委員会は、自明なループ(C11で定義されているか否かに関わらず)に対してコンパイラが診断またはエラーを出力することを要求するルールを検討すべきだった。そうすれば、プログラマは明示的に::yieldなどを挿入する必要があった。隠れたコードはなく、コンパイラが驚くべきことをする機会も減る。
C委員会は、標準規格においてUBのケースを厳密に列挙し、しばしば診断やエラーを要求したり、実装定義の動作に変えたりすることで、各ケースに順番に対処してきた。しかし、そのようなコードを挿入することは考えられないだろう。
- JoshTriplett
> 両方の条件が満たされると、ループ本体はstd::this_thread::yield()の呼び出しに置き換えられます。
ここで叫び声を上げたい。
無限ループで、ライブラリ呼び出しが一切ないものが、システムコールを挿入される。これは待ち受ける恐ろしい驚きだ。
「forward progress guarantee」という概念全体が壊れている。無限ループは無限ループにコンパイルされるべきだ。それ以上でもそれ以下でもない。
- 0x69420
> 言及された提案は欠陥レポートとしても受け入れられたため、実装は以前のC++モードにも修正を適用する可能性があります。そのため、最近のコンパイラではC++20モードであっても古い動作を再現できないことがあります。
ひどい。主要なコンパイラベンダーが、これにいくらか正気をもたらすフラグを提供してくれることを願う。
- omoikane
> ループは自明に空の反復文でなければならない -- つまり、その本体は文字通り空でなければならない
これは、ループ本体が「continue」であってはならないと言っているように思える。実際、-std=c++26で「;」を試したところ、約束通り無限ループになったが、「continue」では未定義動作が復活する:
- "while(true);" -> https://godbolt.org/z/T65o51crx
- "while(true) continue;" -> https://godbolt.org/z/Pj9raEcnP
これは残念だ。なぜなら、セミコロン1つよりも「continue」を好むスタイルガイドを1つ知っているからだ。おそらく、そうしたコードは今後すべて "while(true) {}" と書かれることになるだろう。
https://google.github.io/styleguide/cppguide.html#Formatting...
- ameliaquining
この記事は、残念ながら、なぜ誰もが無限ループをUBにしたいと思うのかを説明していない。私はこの説明を見つけた:https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm
- peterus
マイクロコントローラプログラミングにおいて、無限のwhile(1)ループには有効なユースケースがある(一般通念に反するようだが)。stm32用の自動生成HALコードはエラーハンドラでこれを使用しており、彼らはC++をサポートしているので、これがUBだったとは驚きだ。
私はエラー処理にしか使わないし、もちろん電力に敏感なアプリケーションでこれを使って待機/ストールするのは悪い考えだ。その場合は割り込みからのウェイクアップを使うべきだ。
余談だが、私はエラーハンドラにソフトウェアブレークポイントを含めるのが好きだ。ハードウェアブレークポイント(マイクロコントローラによって物理的に制限されている)を無駄にせずにデバッグが容易になる:
__BKPT();
while (1)
;
- Aurornis
その例でunreachable()関数が実行されるとは全く思わなかった。実際には遭遇しないだろうが、#ifdefの層で奇妙なことが起こるのは見たことがある。
- ErikCorry
> [CのルールがC++で拒否されたのは]有用な最適化を妨げる可能性があるからだ
これらが、この迷惑な新しい動作の代償を喜んで払うと思えるほど私にとって有用な最適化なのか興味がある。
それとも、競合チームと何年にもわたるキャリアを賭けた張り合いをしているコンパイラ作者にとって有用なだけの最適化なのか?
誤解しないでほしい。私自身、競合チームと何年にもわたるキャリアを賭けた張り合いをしたことがある。楽しい。しかし、それがユーザーのためだなどと自分を欺いてはいけない。