JPCERT コーディネーションセンター

FLP07-C. 浮動小数点型を返す関数の返り値はキャストする

浮動小数点型を返す関数の返り値をキャストし、プログラムが確実に、想定通りに動作するようにせよ。

C 標準、6.8.6.4 節、第3パラグラフには次のように規定されている [ISO/IEC 9899:2011]。

式をもつ return 文を実行すると、その式の値を関数呼出し式の値として呼び出し元に返す。式が return 文を囲む関数の返却値の型と異なる型をもつ場合、その値は、その関数の返却値の型をもつオブジェクトへの代入と同じ規則で型変換する。

このパラグラフには次の注釈 (脚注160) がある。

return 文は、代入でない。6.5.16.1 のオブジェクトの記憶域の重なりに関する制約は、関数からの戻りの場合には適用しない。浮動小数点値の表現は、型で暗黙に示されるものより大きい範囲または精度をもつ場合があり、この過剰な範囲と精度を、キャストを使用して削除してよい。

return 式の型が関数の返り値の型と異なる場合、代入式の場合と同じように関数の型への型変換を行う必要があるが、return 式が単に大きい型で評価されるためにより大きい幅の値をもつ場合は、型変換の必要はない。

float f(float x) {
  return x * 0.1f;
}

float g(float x) {
  return x * 0.1;
}

関数 f()float より大きい幅の値を返してもよいが、(より大きい型の定数を使用する)関数 g はそうではない。

C標準では、関数と同じ型をもつ return 式のナローイングを要求していないが、どのような動作が許されるかを明確に規定していない。結果の幅を狭くしてよいのか? それは場合によって許されるのであって、常に許されるわけではないのか? 結果の幅を部分的に狭くしてよいか (たとえば、Application Binary Interface (ABI) が float を double 形式で返すが、float 型の関数が double よりワイドな値として評価される float 型の return 式を持つ場合)? 積極的な処理系であればこれらの問いの全てにイエスと答えるかもしれないが、デバッグやエラー解析の困難な動作になるだろう。

C 標準の脚注160 によると、キャストを使用して過剰な範囲と精度を return 式から削除してよいと規定されている。これはつまり、プログラムの動作を予測可能にするには、浮動小数点数値を返すすべての関数呼出しでキャストを行わなくてはならないということを意味する (関数が直接、暗黙的に変換を行う演算子のような代入式を用意している場合は除く)。型総称数学関数 (tgmath.h) を使う場合、tgmath.h の精度に関するルールを注意深く検討し、どのキャストを行うかを判断する必要がある。予測可能な動作をするコードを書く上で、これらの問題は大きな障害となる。

C 標準、F.6 には次のように規定されている [ISO/IEC 9899:2011]。

return 式が返り値の型とは異なる浮動小数点形式で評価される場合、その式は代入が行われたかのように362関数の返り値の型に変換され、呼び出し側には変換後の値が返される。

この動作は、C 標準の附属書F "IEC 60559 Floating-Point Arithmetic" に適合する処理系のみに適用される。マクロ __STDC_IEC_559__ を使えば処理系が附属書Fに適合しているかどうかを調べることができる。

違反コード

次の違反コード例では、return 文において式の結果をキャストしておらず、返り値の範囲や精度が想定を超えないことを保証していない。この不確定さは、定数 0.1f の使用がもたらすものである。定数は、float より大きい範囲もしくは精度を持つ値として格納される可能性がある。それゆえ、x * 0.1f の結果も、float より大きい範囲または精度を持ちうる。前述のとおり、この範囲や精度は float の範囲や精度に変換できない可能性があるため、calcPercentage() の呼出し元は想定よりも精度の高い値を受け取る可能性がある。これは、プラットフォームによってプログラムの動作が異なる結果となる可能性がある。

float calc_percentage(float value) {
  return value * 0.1f;
}

void float_routine(void) {
  float value = 99.0f;
  long double percentage;

  percentage = calc_percentage(value);
}
適合コード (関数の中)

次の適合コードは、return 文中の式の値をキャストしている。C 標準、5.2.4.2.2 節、第9パラグラフに規定されているとおり、こうすることで戻り値は必ず想定した範囲と精度を持つ[ISO/IEC 9899:2011]。

float calc_percentage(float value) {
  return (float)(value * 0.1f);
}

void float_routine(void) {
  float value = 99.0f;
  long double percentage;

  percentage = calc_percentage(value);
}

関数 calcPercentage() の中で範囲と精度を制限する方法は、calcPercentage() の呼出し毎に修正を適用することなく、1箇所を直せば良い優れた修正方法である。

適合コード (関数の外)

呼び出される関数のソースコードが常に手に入るとは限らない。次の修正方法は、呼び出される関数のソースコードを修正できない場合、calcPercentage() の戻り値を float にキャストすることで範囲と精度を強制的に指定している。

void float_routine(void) {
  float value = 99.0f;
  long double percentage;

  percentage = (float) calc_percentage(value);
}
リスク評価

このガイドラインに従わないと、プラットフォームが違えば結果が異なる可能性がある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
FLP07-C P4 L3
参考情報
[ISO/IEC 9899:2011] Subclause 6.8.6.4, "The return Statement"
Annex F.6, "The return Statement"
[WG14/N1396]
翻訳元

これは以下のページを翻訳したものです。

FLP07-C. Cast the return value of a function that returns a floating-point type (revision 45)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter