局所変数、特に集成体を宣言した後、実際に必要かどうかに関わらず、必ず memset でゼロクリアする人は大勢います。しかし、そんなコードを見かけたら、それを書いた人のコードはすべて疑ってかかった方がよいかもしれません。
まずは、次のコードをご覧ください。
{
int a;
double b;
char *c;
};
A a[10];
memset(a, 0, sizeof(a));
よく見かけるコードですが、上のコードは、必ずしも期待した結果になるとは限りません。なぜなら、double 型やポインタ型は、これらを構成する全ビットが 0 になったとしても、オブジェクトの値が 0 になるかどうかは分からないからです。
確かに、ほとんどの処理系では上記のコードでも問題なく、そして期待通りに動作します。しかし、それはあくまでも"たまたま"動いているに過ぎません。そうした不安定な要素をなくすために行った初期化が、かえってコードを怪しくしてしまっているのです。
単に、集成体の全要素をゼロクリアしたいだけであれば、
とすれば十分です。こう書くと、おそらく次のような反論が返ってくることでしょう。「その方法では、構造体の詰め物がゼロクリアされない」と。しかし、構造体の詰め物にアクセスして、言語仕様上保証される結果を期待することには無理があります。
構造体の詰め物をゼロクリアしたい理由は、多くの場合、memcmp を使って一致判定を行いたいことが理由でしょう。しかし、整数型以外をゼロクリアしても結果が保証されないのと同様、比較の場合もうまくいくとは限りません。具体的には、内部表現が異なる場合でも、値としては同じになるかもしれないからです。
あるいは、こんな反論も聞こえてきそうです。「memset を使った方が効率がよい」と。本当に効率がよいかどうかは実測してみるか、コンパイル結果を見て、ステップ数を計算してみてください。必ずしも memset の方が効率がよいわけではないことに気付くはずです。
仮に memset の方がずっと効率がよかったとしても、こんな汚い方法による最適化は最終手段にすべきです。それに、こんな初期化はそれ自体が不要な場合もあり、単なる"おまじない"に過ぎないことも多いのです。
書籍紹介
- ライト・ポータブル・コード―複数プラットフォームに移植可能なソフトウェアの開発技法
Brian Hook著
- 組込み現場の「C」プログラミング 標準コーディングガイドライン
福岡知的クラスタ(第1期)組込みソフト開発プロジェクト著
- Write Portable Code: An Introduction to Developing Software for Multiple Platforms
Brian Hook著
参考情報
ブックナビゲーション
- 技術情報
- Boost C++ Libraries メモ
- C++と組込み環境
- C++サンプル集
- C++テンプレート集
- C++プログラマのためのC言語入門
- C/C++迷信集
- [迷信] 'A'~'Z' の値は連続している
- [迷信] 0xe-0xe はゼロ
- [迷信] 1 バイトは 8 ビット
- [迷信] 2の累乗による割り算と右シフトは等価
- [迷信] FILE 型は構造体
- [迷信] abs は常に非負の値を返す
- [迷信] argv[0] はプログラム名
- [迷信] char 型は符号付き
- [迷信] double の出力書式は "%lf"
- [迷信] fflush で入力バッファをクリア
- [迷信] free でメモリを開放する
- [迷信] free に NULL を渡すとクラッシュする
- [迷信] gets は単純に fgets に置き換えられる
- [迷信] isalpha 関数の引数は char 型
- [迷信] new に失敗すると NULL が返る。
- [迷信] scanf ではバッファオーバーランを防げない
- [迷信] scanf でキーボードから入力
- [迷信] setjmp マクロの返却値は変数に代入できる
- [迷信] sizeof は定数式
- [迷信] void main(void)
- [迷信] とりあえず memset で初期化
- [迷信] アルゴリズム関数内で関数オブジェクトはコピーされない
- [迷信] オブジェクトの動的生成に失敗するとメモリリークする
- [迷信] コンストラクタから例外を送出してはならない
- [迷信] コンストラクタで自身をゼロクリア
- [迷信] コンパイラはプログラマの心を察してくれる
- [迷信] コンパイルエラーが出るのでアクセス指定子を修正
- [迷信] ソースコード中の即値を全廃せよ
- [迷信] ソースファイルの末尾に }
- [迷信] データ列のソートには qsort 関数を使うべし
- [迷信] プログラムは必ず main から始まる
- [迷信] 一重引用符の中には一文字しか書けない
- [迷信] 今どき int が 16 ビットの処理系なんて無い
- [迷信] 入力データ格納用配列のサイズは BUFSIZ
- [迷信] 割付けたメモリはプログラマが自分で解放しなければならない
- [迷信] 実数型とは浮動小数点型のことである
- [迷信] 引用符で囲んだヘッダ名はカレントディレクトリから探索する
- [迷信] 文字列から整数への変換には atoi
- [迷信] 構造体のタグ名は下線で始める
- [迷信] 構造体はクラスではない
- [迷信] 識別子に使える文字は英数字と下線のみ
- [迷信] 非局所オブジェクトは外部結合
- C99関数・マクロ・前処理スクリプト集
- C言語再入門
- C言語徹底入門
- Drupal メモ
- TOPPERS 情報
- ベターCとしてのC++
- マイコン メモ
- ライブラリ開発入門
- 分割コンパイルをきわめる
- 擬似プロセッサを作る
- 象の卵を探して...
- 車輪の再発明
- 過去の情報


デバッグのためかと
memsetでの0クリアは未初期化によるバグを発見しやすくするのが一番の目的だと思います。
各要素を0クリアしておかないと(別に0でなくてもいいんですけど)、初期化を忘れた際に、再現性が低く追いにくいバグになることがあるので、それを避けるために0クリアしたりすることはあります。
C99だと構造体の初期化演算子があるのでそんなことしなくてもいいんですけど。
私もデバッグのためだと思います。 ですが、私
私もデバッグのためだと思います。
ですが、私の場合の要件は若干異なります。
私の場合の要件は、主にデバッガーで構造体の内容(に限らずメモリの内容)を確認
しながらステップ動作させるような場合の利便性向上を目的としています。
構造体にしろ配列にしろ、不定な値が入っている状況は、肉眼による目視時に余計な
情報、不正な情報との区別が非常に着きにくく、『観察』という目的を阻害します。
構造体の内容が定まった値(0等)で初期化されていないと、正常に動作しないコード
は極力書かない主義ですが、肉眼での観察に対する利便性と、コードの品質管理につ
いては全く別の問題だと考えます。
gccの3.4くらいのバージョンで-Wallを指定した際
gccの3.4くらいのバージョンで-Wallを指定した際
A a[10] = { 0 };
でwarningが出て抑制するオプションが無かったので、memset利用も許して欲しいです。
今は-Wno-missing-field-initializersで抑制できるのでそっち使いますが。
コメントありがとうございます。
gcc 3.4.6で試してみましたが、-Wallを付けても警告は出ませんでした。
警告というのは問題につながるかもしれない怪しいコードを指摘してくれるものですよね。
それなのに、問題のないコードに対する警告を黙らせるために、怪しいコードを書くことで対応するというのはどう考えてもおかしくないですか?
_
警告出たコードは本文に書いてあるやつじゃなくて下記のほうでした
A a = { 0 };
_
そうですね。おかしいですね。あと、警告が出るオプションは -Wall でなくて -Wextra でした。手元のgcc3.4.5のmingwで試しました。失礼しました。
> それを書いた人のコードはすべて疑ってかかった方がよいかもしれません。
この書き方に過剰反応しました。失礼しました。
静的配列ならいいけど・・・
A = (TYPE *)malloc(sizeof(TYPE));
memset(A, 0, sizeof(TYPE));
このようなケースでは型ごとに初期化関数を用意しろということですかね。
Re:静的配列ならいいけど・・・
コメントありがとうございます。
ご指摘のケースでは、TYPEがどんな型なのかにもよるかと思います。
整数型そのもの、または整数型だけをメンバに持つ構造体であれば、mallocのあとにmemsetを呼ぶよりはcallocを使うべきではないでしょうか?
整数型以外を含む構造体の場合、そうしたメンバが少なければ、callocのあとで、そのメンバだけ代入してもよいでしょう。
あるいは、処理系に完全に依存したモジュールであれば、memset等で初期化しても実質的な問題はないと思います。
C99なら複合リテラルを代入するという方法もないわけではありません。
あと、C++であれば、A = new TYPE(); で済みますね。
いずれの場合も、その初期化が本当に必要であれば、ということが前提です。
ゼロクリアした直後に、別途各メンバに値を代入するようなコードが結構多いですから。
>
> 整数型そのもの、または整数型だけをメンバに持つ構造体であれば、mallocのあとにmemsetを呼ぶよりはcallocを使うべきではないでしょうか?
内部仕様が公開されていない mbstate_t 型の変数を動的に割り当てたい場合は
どのように対処されますか?
それに、malloc+memsetをcallocに変更するだけでは、
以下の問題をクリアできないので、全く以て解決策になっていないと思います。
# よく見かけるコードですが、上のコードは、必ずしも期待した結果になるとは限りませ
# ん。なぜなら、double 型やポインタ型は、これらを構成する全ビットが 0 になったと
# しても、オブジェクトの値が 0 になるかどうかは分からないからです。
#
# 確かに、ほとんどの処理系では上記のコードでも問題なく、そして期待通りに動作しま
# す。しかし、それはあくまでも"たまたま"動いているに過ぎません。そうした不安定な
# 要素をなくすために行った初期化が、かえってコードを怪しくしてしまっているのです
コメントありがとうございます。
> 内部仕様が公開されていない mbstate_t 型の変数を動的に割り当てたい場合は
> どのように対処されますか?
mbstate_t型はオブジェクト型だとしか規定されていませんので(たまたま整数型、または整数型だけで構成される修正型の場合もありますが)、
> 整数型そのもの、または整数型だけをメンバに持つ構造体であれば、
には該当しません(もちろん、特定処理系に特化するつもりであれば該当することもあり得ます)。
上記は、あくまでも、malloc + memoryが使える状況であれば、わざわざ2つの関数に分けるのではなく、callocを使えばよいのでは、ということです。
mbstate_t型をmallocやcalloc等で割りつける状況はそう多くありませんが、どういう割付け方法をとるにせよ、本来であれば、
mbrtowc(NULL, "", 1, &state);
のようにして初期化するのが筋です(シフトシーケンスを使わないことがわかっていれば、ここまでしなくてもよいとは思いますが)。
mbstate_t型に限らず、内部表現が明確になっていない型の場合は、それを初期化する関数やマクロがふつうは用意されていますし、用意すべきですね。
補足
mbstate_t型の場合、ゼロクリアしたとしてもそれが初期変換状態とは限りませんので、ゼロクリア自体が不要な典型的な例かと思います。訂正:値0を持つmbstate_t型は初期状態でした。多次元配列の初期化では、mbstate_t型の変数をゼロクリアする例を挙げましたが、それはあくまでも内部表現が分からない型の変数をゼロクリアする方法であって、それで初期変換状態になるという話ではありません。
{}では十分ではない?
集成体の0初期化は A a[10] = {};で十分だと思っていましたが、
文法を見た所、これが通用するのはC++だけのようですね。
VC++だと A a[10] = { 0
VC++だと
A a[10] = { 0 };
も結局memset呼ぶ出しになるんですよね
しかもpragma intrinsic指定でも関数呼び出しになってCRT使わないコード書いてるとき悩む悩む
コメントありがとうございます。
> A a[10] = { 0 };
> も結局memset呼ぶ出しになるんですよね
確かにそういう処理系は少なくありません。
他に、構造体の代入時に memcpy を使うものとか。
なので、memset を使うべきかどうかはコンパイラ任せにすればよいのです。