• ベストアンサー

入れ子になった構造体について

以下のように定義した、2重に入れ子になった構造体があります。 これを、mallocを使ってエリアを確保した後、初期化しています。 例では、各構造体の項目数が少ないのですが、 項目が増えた場合、下記のような初期化方法だと面倒だと思います。 もっとよい方法があるのだろうと思っているのですが・・・。 下記は私の試行錯誤結果なので、「普通はこうやるんじゃないの?」という方法などがあればご教授願います。 /* -- 構造体定義 -- */ typedef struct {   char data[16] ; }D_TAG ; typedef struct {   char name[16];   D_TAG *d ; }F_TAG ; typedef struct {   char name[16] ;   F_TAG *f ; }T_TAG ; /* -- 変数定義 -- */ T_TAG *t ; /* -- エリア確保 -- */ t=(T_TAG*)malloc(sizeof(T_TAG)*10); for(i=0;i<10;i++) {   t[i].f=(F_TAG*)malloc(sizeof(F_TAG)*10);   for(j=0;j<10;j++) {     t[i].f[j].d = (D_TAG*)malloc(sizeof(D_TAG)*10);   } } /* -- 初期化 -- */ for(i=0;i<10;i++) {   memset(t[i].name , 0x00, sizeof(t[i].name));   for(j=0;j<10;j++) {     memset( t[i].f[j].d, 0x00, sizeof(D_TAG)*10);     memset( t[i].f[j].name , 0x00, sizeof(t[i].f[j].name));   } } よろしくお願いいたします。

質問者が選んだベストアンサー

  • ベストアンサー
  • TALLY-HO
  • ベストアンサー率29% (103/354)
回答No.1

初期化値が0なら、mallocではなくcallocでメモリ確保 されてはどうでしょう?自動で0に初期化してくれますよ。

参考URL:
http://www.bohyoh.com/CandCPP/C/Library/calloc.html
sunasaka3
質問者

お礼

早速のご回答ありがとうございます。 「calloc」は使ったことが無かったです。 早速使ってみました。ありがとうございます! ちなみに、もし、スペースなど0以外で全て初期化したいような場合は、地道にやることになるのでしょうか?

その他の回答 (4)

  • VirtualT2
  • ベストアンサー率58% (18/31)
回答No.5

なんとなく、sunasaka3さんのやりたい事が 見えてきたような思い込みを当方が勝手にしたので 参考程度に。 T_TAG/F_TAG/D_TAGの各内部が動的で必要数も変わるんですな。 高速化の手助けには成らないですけど、まとめ方の例として 線形リストを用いて観てはいかがでしょうか。 例(あえて片方向線形リストっ) typedef struct _tag_N_TAG { // データ格納構造体  _tag_N_TAG *pnext; // 自分の次に来るN_TAG構造体へのポインタ(NULL時、終末)  _tag_N_TAG *pdata; // 更に下に格納してるデータ(NULL時、下にデータ無し)  char *name; // 格納してる名前 } N_TAG; // N_TAGを確保し初期化、線形リストに追加する処理 // **pTOP: この横階層のトップ(絶対!でないとリストが破綻する(横着してるから… // name : 名称 N_TAG* CreateN_TAG(N_TAG **pTOP, char *name) {  N_TAG *p;  if( !(p = (N_TAG*)malloc(sizeof(N_TAG))) ) return(NULL); // メモリ不足  if( !(p->name = (char*)malloc(strlen(name)+1)) ) { // 名前の領域を確保   free(p); return(NULL); // メモリ不足  }  p->pnext = *pTop; // 作成したpの次を引数である先頭にし  *pTop = p; // 作成したpが先頭になる  p->data = NULL; // 作成したばかりなので下にデータは無し  strcpy(p->name, name); // 名前をコピーする  return(p); // 作成したN_TAGであるpを返す } 使用例 void main(void) {  N_TAG *root = NULL;  N_TAG *p;    p = CreateN_TAG(&root, "a"); // root階層に付け足す  CreateN_TAG(&root, "b"); // 付け足す(リストをたどれば  CreateN_TAG(&root, "c"); // 足す   作成したN_TAGを探せるのでポインタ捨てる)    CreateN_TAG(&p->pdata, "m"); // aの下方階層に付け足す  CreateN_TAG(&p->pdata, "n"); // 付け足す  p = CreateN_TAG(&p->pdata, "o"); // 足す    CreateN_TAG(&p->pdata, "q"); // oの下方・・・ry    // cを探す(nとか下の階層だと、関数作らないとダメなので…横着…(;;  p = root;  while(p) { if(!(strcmp(p->name, "c"))) break; p = p->pnext; } // こないなカンジ  if(p) { /* pは、"c"のN_TAGポインタ */ }    // 後始末… 書かなきゃダメか…  ReleaseN_TAG(root); // 下記 } // pR:階層のトップで無いとダメ。 void ReleaseN_TAG(N_TAG *pR) {  N_TAG *p, *q;  p = pR;  while(p) {   if(p->pdata) ReleaseN_TAG(p->pdata); // 下方階層があるなら、再帰する   q = p; // 開放用   p = p->pnext; // 次を取り出す   free(q->name); // 名称領域開放   free(q); // 自身開放  } } // ----------------------------------------------------------------------------- イメージ的には       ↓pnextね  root == c -> b -> a -> NULL (横階層 pnext->pnext…)           | ←pdataね(下階層 pdata)           o -> n -> m -> NULL           |           q -> NULL てな、カンジの構造が作れます。 CreateN_TAG関数は、常に先頭に足していく様に作ったので 上記のようなデータの並びになります。 データの取り出しは、rootからpnextやpdataをたどっていきます。(開放もね) 途中のデータを削除・追加・ソートとか考えてない作りです。(この例はね) 理解できれば、双方向な線形リスト化や、 削除・追加関数・ソート関数の作成も容易になります。 PS.判りづらいかも… ++余計な、おせっかいかも +++すみません…(TT ++++コードにロジックエラーが在るかも…

sunasaka3
質問者

お礼

線形リストですか。 情報処理とかの勉強で出てきますが、実際にやってみたことは無かったです。 たしかに、やりたいことを図示するとリストになるのですよね。 やってみたいと思います。 ありがとうございました!

  • nitscape
  • ベストアンサー率30% (275/909)
回答No.4

なんとなく構造が複雑でプログラミングが進んでいくとメモリリークを起こしてしまいそうですね。入れ子を使わずに typedef struct {   char data[16];   char name1[16];   char name2[16]; }T_TAG ; のようにしてしまってはダメなのですか?後々の処理でdataやnameのポインタが移動するからというのでしたら typedef struct {   char* data;   char* name1;   char* name2; }T_TAG ; のようにしてポインタ扱いにしてしまってもいいと思いますし... この手の入れ子を使わなくてはいけないというような条件がないのでしたら見やすいコードにするとメモリリークやバグの可能性も減らせていいかと思います。

sunasaka3
質問者

お礼

アドバイスありがとうございます。 自分としてもあまり複雑にはしたくなかったのですが、方法が思い浮かばず・・・。 今回、このような構造にしたのは、それぞれ入れ子になっている構造体のサイズが、nameの配列要素ごとに違っている為です。 なので、実際は、質問のように、まとめてmalloc後、纏めて初期化ということはしていません。 イメージとしては、以下のようにDBっぽい扱いです。  ・T_TAG→テーブル名を格納  ・F_TAG→各テーブルのフィールド名を格納  ・D_TAG→各フィールドのデータを格納 テーブル毎にフィールド数が異なり、各テーブルでデータ数が異なるものを纏めて扱うことが目的です。 テーブル名、フィールド名にあたるものはも外部ファイルから取得して動的に作成しなければならないのです。 なんか、無理やり感が在るのですが・・・。 いい方法があればアドバイスいただければ幸いです。 よろしくお願いいたします。

  • VirtualT2
  • ベストアンサー率58% (18/31)
回答No.3

>ちなみに、質問では省いたのですが、freeはmallocの逆順に入れ子の内側からやっていく、ということになるのでしょうか? >(今は、そうしています。) 補足回答です。 ならべくなら、入れ子の内側からの方が良いです。(確保した順番の逆の意) 開放メモリの分断化を防ぐ観点からはですが… しかしながら、メモリアロケイターが確保の逆順に開放したからといって、 必ずしも、最適にメモリを管理してくれてる保証はないので、何とも言えません。 (確保時にメモリセル自体が分断化された状況ってのありうるし…) 今回、質問した例だと内側から開放していかないといけないですね。 (外側からだと、内側で確保したメモリポインタが参照できなくなってしまうから…(^-^) 念の為(しつこい?)外側から開放した場合… F_TAG型のt[0].f[1]を開放してしまったら、 D_TAG型のt[0].f[1].dに格納されていたメモリ確保ポインタが参照・開放出来なくなってしまう。 ※開放する前に他で取って置けば別ですが… 具体例: char *a = (char*)malloc(10); char *b = (char*)malloc(10); free(a); free(b); ってのは、アリです。(釈迦に説法っぽぃなぁ…) と、省かれた質問のアドバイスでした。 更に補足~ #2で書いたC++部 delete[] t; ってのは、 delete時にT_TAGのデスクトラクター~T_TAG()が呼ばれ、 ~T_TAG()の中から、~F_TAG()が呼ばれ、 ~F_TAG()の中から、~D_TAG()が呼ばれます。 そして、戻っていく訳なので… つまり、最内側から開放されていく訳です。

sunasaka3
質問者

お礼

確認できて安心しました。 ありがとうございました!

  • VirtualT2
  • ベストアンサー率58% (18/31)
回答No.2

#1さんのcalloc使用に同意しますね。 各構造体毎に初期化マクロを用意するというのも、手です。 #define m_clrD_TAG(prm) memset(prm->name,0,sizeof(prm->data)); #define m_clrF_TAG(prm) { memset(prm->name,0,sizeof(prm->name)); prm->d = NULL; } #define m_clrT_TAG(prm) { memset(prm->name,0,sizeof(prm->name)); prm->f = NULL; } m_clrT_TAG(&t[0]) とか。 まあ、一部クリアーとか発生するような状況でもない限りは…(ポインタもクリアーしてるから、リークとかに注意) 以下は、とっても暇な時にでも… C++なら構造体にコンストラクターつけて、 クリアーをする手があります。 へたっぴな、C++で申し訳ないですが あくまで御参考に~。(Cオンリーなら、全然参考にならないデス) 定義 class D_TAG { public: char data[16];  D_TAG() { memset(data, 0, 16); } // newで作成する時に自動的に呼ばれる  ~D_TAG() {} }; class F_TAG { public: char name[16];  D_TAG* d;  F_TAG() { memset(name, 0, 16); d = NULL; }  ~F_TAG() { if(d) delete[] d; } // delete時に呼ばれる。dが確保されているなら開放する }; class T_TAG { public: char name[16];  F_TAG* f;  T_TAG() { memset(name, 0, 16); f = NULL; }  ~T_TAG() { if(f) delete[] f; } }; 初期化使用例 T_TAG* t = new T_TAG[10]; // newで作成時に初期化される for(int i=0;i<10;i++) {  t[i].f = new F_TAG[10];  for(int j=0;j<10;j++) {   t[i].f[j].d = new D_TAG[10];  } } 開放例 delete[] t; // コレだけで全部まとめて開放してくれる(tの中のfもfの中のdもね) delete[] t[1].f[5].d; t[1].f[5].d = NULL; // コレは、必ず! ※[T_TAGのf][F_TAGのd]は、各個に開放したら必ずNULLを入れないといけない。 PS. C++へのステップアップにと勝手に私が思い込んでみました。 ご迷惑でなければ良いのですが… 「C言語しか環境的につかえんのだよ、だから質問してるんだよ!」 って、実は!?だったら、すみません。 組み込みexeGCCでヒープが湯水のようにつかえない、とかだったり…

sunasaka3
質問者

お礼

ご回答ありがとうございます。 今回は、「Cオンリー」なのです・・・。 C++の方は今後の参考とさせていただきます。 callocを使ってやってみたのですが、遅くなってしまいちょっと微妙な状況です。 もともと3秒くらいかかって「遅い」といわれていたのですが、callocだと5秒かかってしまい・・・。 ですので、マクロを使用する方法で試してみます! ちなみに、質問では省いたのですが、freeはmallocの逆順に入れ子の内側からやっていく、ということになるのでしょうか? (今は、そうしています。)

関連するQ&A