プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 2

同カテゴリーの次の記事

プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 3

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


2. 0 よりも大きいは 1 を意味しない

CoreCLR プロジェクトから抜粋した以下のコードについて考えてみます。このコードにはエラーが含まれています。PVS-Studio アナライザーは、次の診断を出力します。

V698 Expression ‘memcmp(….) == -1’ is incorrect. This function can return not only the value ‘-1’, but any negative value. Consider using ‘memcmp(….) < 0' instead. (V698 式 'memcmp(....) == -1' は不正です。この関数は、'-1' だけでなく、任意の負の値を返すことができます。代わりに、'memcmp(....) < 0' の使用を検討してください。)

bool operator( )(const GUID& _Key1, const GUID& _Key2) const
  { return memcmp(&_Key1, &_Key2, sizeof(GUID)) == -1; }

説明

memcmp() 関数の説明を見てみましょう。

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

ptr1 で示されるメモリーブロックの最初の num バイトと ptr2 が指す最初の num バイトを比較し、すべて一致する場合は 0 を、そうでない場合はどちらのほうが大きいかを表す非ゼロ値を返します。

戻り値:

  • < 0 - 一致しない最初のバイトの ptr1 の値のほうが ptr2 の値よりも小さい (符号なし char 値として評価した場合)。
  • == 0 – 両方のメモリーブロックの内容が等しい。
  • > 0 – 一致しない最初のバイトの ptr1 の値のほうが ptr2 の値よりも大きい (符号なし char 値として評価した場合)。

ブロックが同じでない場合、関数は 0 よりも大きい値または小さい値を返します。大きいまたは小さいということが重要です。そのような関数の結果を memcmp()strcmp()strncmp() などを使用して定数 1 と -1 と比較することはできません。

興味深いことに、結果を 1/-1 と比較する不正なコードは、プログラマーが期待するように動作するかもしれません。しかし、それは運次第です。関数の動作は、予期せず変わる可能性があります。例えば、コンパイラーを変更したり、プログラマーが memcmp() を新しい方法で最適化したりすると、コードが動作しなくなります。

正しいコード

bool operator( )(const GUID& _Key1, const GUID& _Key2) const
  { return memcmp(&_Key1, &_Key2, sizeof(GUID)) < 0; }

推奨事項

現在、関数が動作する方法に依存しないことです。関数が 0 より小さい値または大きい値を返すとドキュメントに記載されている場合、関数は -10、2、あるいは 1024 を返すことがあります。戻り値が常に -1、0、または 1 だったとしても、それは何の証明にもなりません。

ところで、関数が 1024 のような値を返す可能性があるということは、memcmp() の実行結果を char 型で保存できないことを示しています。これもよくあるエラーで、その影響は重大です。このようなミスは、MySQL*/MariaDB* 5.1.61、5.2.11、5.3.5、5.5.22 よりも古いバージョンで重大な脆弱性の原因となりました。ユーザーが MySQL*/MariaDB* に接続すると、コードはトークン (パスワードとハッシュからの SHA) を評価して、memcmp() 関数の想定される値と比較します。しかし、一部のプラットフォームでは、戻り値が [-128..127] の範囲外になります。その結果、256 分の 1 の確率で、ハッシュ値に関係なく、ハッシュと想定値の比較が常に true を返します。そのため、ハッカーが単純な bash コマンドを利用して、パスワードを知らなくても root 権限で MySQL* サーバーにアクセスできてしまいます。この問題は、’sql/password.c’ ファイルの次のコードが原因でした。

typedef char my_bool;
...
my_bool check(...) {
  return memcmp(...);
}

この問題の詳細は、「MySQL*/MariaDB* のセキュリティーの脆弱性」 (英語) を参照してください。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]
  • マルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発するマルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発する コードの並列化にはさまざまな手法があります。この記事では、インテル® Parallel Composer で利用可能な手法の概要を説明し、各手法の主な長所を比較します。インテル® Parallel Composer は Windows* 上の C/C++ を使用した開発のみを対象としていますが、これらの手法の多くは Fortran や […]
  • インテル® AVX を使用した IIR ガウスぼかしフィルターの実装インテル® AVX を使用した IIR ガウスぼかしフィルターの実装 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「IIR Gaussian Blur Filter Implementation using Intel® Advanced Vector Extensions」の日本語参考訳です。 ソースのダウンロード: gaussian_blur.cpp […]