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

その他インテル® DPC++/C++ コンパイラー特集

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


31. C および C++ では配列は値渡しされない

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

V511 The sizeof() operator returns size of the pointer, and not of the array, in ‘sizeof (src)’ expression. (V511 ‘sizeof (src)’ 式において、sizeof() 演算子は配列ではなく、ポインターのサイズを返します。)

ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
  memcpy( mat, src, sizeof( src ) );
}

説明

プログラマーは、C/C++ では関数へ配列を値渡しできないことを忘れることがあります。配列へのポインターを引数として渡されなければなりません。大括弧で囲まれた数字は意味がなく、渡される配列サイズをプログラマーに知らせるヒントでしかありません。実際、全く異なるサイズの配列を渡すことができます。例えば、次のコードは問題なくコンパイルされます。

void F(int p[10]) { }
void G()
{
  int p[3];
  F(p);
}

さらに、sizeof(src) 演算子は、配列サイズではなく、ポインターのサイズを評価します。その結果、memcpy() は配列の一部のみコピーします。ポインターのサイズに応じて、4 または 8 バイトがコピーされます (一般的でないアーキテクチャーではこの限りではありません)。

正しいコード

最も単純なバージョンは、次のようになります。

ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
  memcpy(mat, src, sizeof(float) * 3 * 3);
}

推奨事項

コードをよりセキュアにするいくつかの方法があります。

配列サイズが分かっている場合: 関数に配列への参照を渡します。ただし、これが可能であることを知っているプログラマーは少なく、その記述方法を知っているプログラマーはさらに少数です。そのため、この例が皆さんの役に立つことを願っています。

ID_INLINE mat3_t::mat3_t( float (&src)[3][3] )
{
  memcpy( mat, src, sizeof( src ) );
}

これで、適切なサイズの配列のみ関数に渡すことができます。最も重要なことは、sizeof() 演算子がポインターではなく、配列のサイズを評価することです。

この問題を解決する別の方法は、std::array (英語) クラスを使用することです。

配列サイズが不明な場合: プログラミングに関する書籍の著者の中には、std::vector (英語) クラスやその他の類似するクラスの使用をアドバイスする方がいますが、実際には常に役立つとは限りません。

単純なポインターのほうが良い場合があります。その場合、ポインターと要素数の 2 つの引数を関数へ渡すべきです。しかし、一般にこれは不適切な手法で、多くの問題を引き起こす可能性があります。

そのような場合、「C++ コア・ガイドライン」 (英語) で紹介されている考察が参考になります。また、「配列を単一のポインターとして渡さない」 (英語) もお読みになることを推奨します。「C++ コア・ガイドライン」は、時間のあるときに目をとおしておくと良いでしょう。多くの役立つアイデアが含まれています。

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

タイトルとURLをコピーしました