2009年05月27日

クラスのメンバ初期化時の注意事項

こんにちは、イワムーですわーい(嬉しい顔)

今日はプログラムの話でもしてみようと思いますパンチ

C++ でクラスのデータメンバ(メンバ変数)の値を初期化したい場合、皆さんはどのように実装されていますか?

様々な方法が考えられると思いますが、
memset() の使用に関しては時として注意が必要になりますexclamation×2

#include < string.h >
void *memset(void *s, int c, size_t n);


memset() は s で示されるメモリ領域の先頭から n バイトを c で埋める関数です。

文字を格納するバッファを特定の文字で埋めておきたい場合など、便利ですよねペン

char buff[6];
memset(buff,'H',sizeof(buff));


同様にクラスオブジェクトに対しても memset() は使用できますが、そのクラスが virtual メソッドを含んでいた場合は注意が必要ですexclamation

#include < string.h >
#include < memory.h >
#include < iostream >

class Hexa {
public:
   Hexa(){}
   ~Hexa(){}
   virtual void setStr(void){}

protected:
   char _str[0x10];
};

class Hexa2 : pubilc Hexa {
public:
   Hexa2(){}
   ~Hexa2(){}
   void setStr(void){ strcpy(_str,"hexadrive"); }
   char* drawStr(void){ return _str;}
};

int main(void)
{
   Hexa2* pHexa = new Hexa2();
   memset( (void*)pHexa, 0, sizeof(Hexa2) );
   pHexa->setStr(); // ここでクラッシュする
   cout<drawStr();
   retuen 0;
}


このプログラムを実行するとsetStr()を呼び出す時点でクラッシュしますあせあせ(飛び散る汗)
なぜなら setStr() が virtual メソッドになっているからです。

C++ではクラスのメソッドに virtual キーワードをつけて宣言する(仮想関数)と、自動的に「関数ポインタの配列」(のポインタ)がメンバ変数領域に作られます。
これを仮想関数テーブル(vtable)と呼びますひらめき
仮想関数テーブルの詳細はこちら

実行時に動的に呼び出すメソッドを決める必要がある、virtual メソッドはこのvtableで実現されています。
通常はプログラマーはvtableにはアクセスできませんが、memset()をvirtualメソッドを持ったクラスオブジェクトに適用してしまうと、上記例ではvtableの中身までゼロクリアされてしまうため、アドレスがゼロになっている関数を呼び出そうとした時点でクラッシュしてしまうのですバッド(下向き矢印)
(コンパイラの最適化オプション等の影響でクラッシュしない場合もあります)

ではどうやってメンバを初期化したら良いのかというと、やはりデフォルトコンストラクタや初期化メソッドで各メンバの初期値を入れるのが良いのではと思いますわーい(嬉しい顔)

クラスのメンバ初期化時には意識してみてくださいねるんるん



posted by 管理人 at 18:56 | Comment(2) | プログラミング
この記事へのコメント
プログラミングしててZeroMemoryで変な挙動が起きてしまい困っていたところで、この記事を見つけました。ZeroMemoryを書かないと、エラーが起きないのでなんでかな?と思っていた疑問が解決して、助かりました!
Posted by Pekurusu at 2009年11月03日 14:00
コメントありがとうございます。
陥りやすい危険なバグなので、無事に解消されたようでなによりです^^
今後もお役に立てるような情報を掲載できればと思います、宜しくお願いします!
Posted by ヘキサドライブ イワムー at 2009年11月05日 15:21
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。