HN 日本語サマリー

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

C++のfloatからintへの変換は未定義の動作となる可能性がある

C++ float-to-int conversion can be undefined behavior (kttnr.net)

41 pointsby signa1139 コメント

要約

C++では、浮動小数点数(float)を整数型(int)に変換する際に、変換先の整数型に値が収まらない場合、未定義の動作(Undefined Behavior, UB)となります。この変換はコード上で簡単に行えてしまうため、多くのコードで意図せずこの問題が発生しています。コンパイラやハードウェアによっては問題なく動作するように見えても、異なる環境では予期せぬ結果を招く可能性があります。

全文翻訳

C++のfloatからintへの変換は未定義の動作となる可能性がある C++でfloatをintに変換する際、切り捨てられたfloatが宛先整数型に収まらない場合、未定義の動作となります。C++では、これを誤って行うことが容易です。多くのコードがこれを間違っています。 void foo(float f) { int i0 = f; int i1 = int(f); int i2 = static_cast<int>(f); } このコードは、-Wallや-Wextraを使用しても、警告を一切生成しません。-Wconversionは暗黙の変換についてのみ警告します。しかし、3つの変換のそれぞれが、一部の入力に対して未定義の動作となります。 Cppreferenceの暗黙の変換ページ、「浮動小数点-整数変換」のセクションには次のように記載されています。 浮動小数点型のprvalueは、任意の整数型へのprvalueに変換できます。小数部分は切り捨てられます。つまり、小数部分は破棄されます。値が宛先型に収まらない場合、未定義の動作となります(宛先型がunsignedであっても、モジュロ演算は適用されません)。 私はこの間違いを現場で何度も見てきました。例えば、C++ Core GuidelinesをサポートするMicrosoftのGuidelines Support Libraryなどです。 GSLは、安全なナローイング変換のための関数、gsl::narrowを提供しています。 gsl::narrow<T>(x)は、ナローイング変換のための名前付きキャストであり、符号なしプロモーションなしでstatic_cast<T>(x)を実行します。引数xがターゲット型Tで表現できない場合、関数は例外をスローします。 floatからintへの変換をどのように処理しているのか興味がありました。ドキュメントとは異なり、実際にはそれらを処理していないことが判明しました。gsl::narrowも一部の入力に対して未定義の動作となります。私はコメントでこれを指摘しましたが、以下の理由で却下されました。 内部でのUBの使用について:それは問題ありません。もし誰かがそれを心配するなら、使用されているUBはターゲットとするプラットフォームでは無害です(例えば、これらの型に対するハードウェアトラップ表現にヒットすることはありません)。 未定義の動作は現実のものですが、現在のプロセッサとコンパイラでは、プログラムは通常それでも動作します。コンパイラはx86のCVTTSS2SIのような命令を選択する傾向があり、これは表現不可能なすべての入力を同じプレースホルダー整数INT_MINにマッピングします。AArch64にはFCVTZSがあり、これは飽和させ、NaNをゼロにマッピングします。 プログラムがクラッシュしないと、問題は無害に見えることがあります。しかし、そうではありません。異なるハードウェアで異なる結果が得られることは問題です。さらに重要なのは、実行される未定義の動作は問題があるということです。Ralf Jungは、彼の投稿「What The Hardware Does」is not What Your Program Doesでこれをうまく説明しています(彼のブログをお勧めします)。コンパイラが異なる変換を適用した場合、コードが突然機能しなくなる可能性があります。 正しい修正は、キャストする前に範囲チェックを行うことです。私はこれをRustの飽和アプローチに基づいた概念実証ライブラリにしました。 ClangとGCCの未定義動作サニタイザー、-fsanitize=float-cast-overflowを参照して、未定義の動作を検出することもできます。私はUBSanですべてのC++コードをテストすることを推奨します。 誤ったGSLの理由は、問題コメントからコードにまで及んでいます。問題は修正されていません。