局所変数、特に集成体を宣言した後、実際に必要かどうかに関わらず、必ずmemset関数でゼロクリアする人は大勢います。しかし、そんなコードを見かけたら、それを書いた人のコードはすべて疑ってかかった方がよいかもしれません。
まずは、次のコードをご覧ください。
0 1 2 3 4 5 6 7 8 9 |
struct A { int a; double b; char *c; }; A a[10]; memset(a, 0, sizeof(a)); |
よく見かけるコードですが、上のコードは、必ずしも期待した結果になるとは限りません。なぜなら、double型やポインタ型は、これらを構成する全ビットが0になったとしても、オブジェクトの値が0になるかどうかは分からないからです。
確かに、ほとんどの処理系では上記のコードでも問題なく、そして期待通りに動作します。しかし、それはあくまでも”たまたま”動いているに過ぎません。そうした不安定な要素をなくすために行った初期化が、かえってコードを怪しくしてしまっているのです。
C言語で、単に集成体の全要素をゼロクリアしたいだけであれば、
0 1 2 |
A a[10] = { 0 }; |
とすれば十分です。このように書くと、おそらく次のような反論が返ってくることでしょう。「その方法では、構造体の詰め物がゼロクリアされない」と。しかし、構造体の詰め物にアクセスして、言語仕様上保証される結果を期待することには無理があります。
構造体の詰め物をゼロクリアしたい理由は、多くの場合、memcmp関数を使って一致判定を行いたいことが理由でしょう。しかし、整数型以外をゼロクリアしても結果が保証されないのと同様、比較の場合もうまくいくとは限りません。具体的には、内部表現が異なる場合でも、値としては同じになるかもしれないからです。
あるいは、こんな反論も聞こえてきそうです。「memset関数を使った方が効率がよい」と。本当に効率がよいかどうかは実測してみるか、コンパイル結果を見て、ステップ数を計算してみてください。必ずしもmemset関数の方が効率がよいわけではないことに気付くはずです。
仮にmemset関数の方がずっと効率がよかったとしても、こんな汚い方法による最適化は最終手段にすべきです。それに、こんな初期化はそれ自体が不要な場合もあり、単なる”おまじない”に過ぎないことも多いのです。
ところで、先ほどはC言語の例でしたが、C++であれば、
0 1 2 |
A a[10] = {}; |
のように波括弧の中に0をひとつも書かなくてもかまいませんし、C++11以降であれば、
0 1 2 |
A a[10]{}; |
のように書くこともできます。