- ベストアンサー
どのようなレコード構成でもfillerという項目はスペースで埋める関数
C言語の話です。 A,B 二つのファイルにテキストのデータを書き込みます。 レコードを何件も書き込んでいくのですが、レコードとレコードは連続して書き込んでいきます。 各ファイルのレコードの構成を表す構造体が次のように定義されています。 A_RECのほうはAファイルで、B_RECのほうはBファイルです。 typedef struct { char rec_kbn[1]; char num[16]; char filler[3]; }COM, *COMp; typedef struct { COM com; char a1[8]; char a2[2]; char a3[9]; char filler[11]; }A_REC, *A_RECp; typedef struct { COM com; char b1[3]; char b2[15]; char filler[22]; }B_REC, *B_RECp; AとBのレコード構成は違いますが、 fillerというメンバは必ず空白(スペース)で埋めなくてはいけません。 fillerというメンバに対して何もしなかったら、そこには何が入っているのでしょうか。 ヌル文字('\0')で埋まっているのでしょうか。それとも、特定できないごみでしょうか。 空白で埋まっていないなら、空白で埋めることになるのですが、 fillerを空白で埋めるという操作をまとめて一つの関数にできないでしょうか。 どんな構造体(あるいは構造体のポインタ)を受け取っても、fillerというメンバは空白で埋めるという関数は作れますか。 やはり、fillerの位置やサイズは引数として受け取らなければ、そのような関数は作れませんか。 typedef struct { char rec_kbn[1]; char num[16]; char filler[3]=" "; /* ダブルクォーテーションの間はスペース3つ */ }COM, *COMp; このようにすることはできますか。できるならば、ちゃんと意図したとおり、空白で埋められているのでしょうか。 なお、*COMpや*A_RECpや*B_RECpですが、それは使わなくてもきまりごととして書くことになっています。 よろしくお願いいたします。
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
こんにちは。itohhといいます。 補足の内容からすると、 1.レコードの内容が設定されたものが、どこからか渡ってくる。 2.レコードの種類も渡ってくる。(A_REC?B_REC?) 3.渡ってきた段階では、Fillerには、何が入っているか保証されない。 4.2種類を超えるレコードの種類数がある。 一番いい方法は、レコードを設定する側でスペースクリアするのが常道だと思うのですが... 2種類ぐらいだと、マクロも良いかもしれませんが、何種類もあるとすると、汎用的にはなかなかいきませんよね。 (だから、ここに質問しているのだと思いますが...) こういう案は、如何ですか? 1.レコード毎にFillerの位置とサイズをテーブルにする。 2.関数には、レコードの設定されているエリアのアドレスと、レコード種類を渡す。 3.テーブルを参照してスペースを詰める。 ちょっと、力業っぽいところが、ありますが... こういう方法なら、net0505さんはわかってらっしゃるとは思うのですが、念のため。 typedef struct { int mCD; int mOffset; int mLen; } strFiller; // レコード種類毎のテーブル static strFiller FilRec[] = { { 1, 17, 3}, // A_REC(COM) { 1, 39, 11}, // A_REC { 2, 17, 3}, // B_REC(COM) { 2, 38, 22}, // B_REC {-1, -1, -1} // EOF }; void FillerInt(char* pRec, int pRecCD ) { int i=0; For( i=0; FilRec[i].mCD != -1; i++ ) { if( pRecCD == FilRec[i].mCD ) { memset( pRec+FilRec[i].mOffset, ' ', FilRec[i].mLen ); } } } void main() { A_REC *a=NULL; B_REC *b=NULL; Set_aRec(a); // どっからか、設定されてくる。 FillerInt((char*)a, 1); Set_aRec(b); // どっからか、設定されてくる。 FillerInt((char*)b, 2); }
その他の回答 (8)
- leaz024
- ベストアンサー率75% (398/526)
> 「文字配列の初期値つき定義において > char str[3]="ABC"; > のように、配列の大きさと文字列の長さが等しい場合、 > '\0'が付加されることなく初期化される。」 すみません、すっかり記憶から抜け落ちていました。 確かに上の記述で、間違いなくchar型3つ分のみの初期化が行われます。 だいたい初期化子が多いと、コンパイルエラーになっちゃいますもんね。 ところで No.6 の補足より > レコードを作成するもとになるデータを取得する。 と書かれていますが、この部分は出来ているのですか?(また、どこから取得してくるのでしょう?) もし出来ているのなら、データの識別はどのように行っているのでしょうか? データのサイズが異なるとなると、取得する部分自体が、それを踏まえてデータを読まないといけない気がするのですが。(A_RECとB_RECって、サイズ違いますよね?<間違い?)
お礼
おっしゃるとおりですね。
- natural
- ベストアンサー率37% (419/1115)
naturalと申します。 質問を読ませていただいたところ、fillerというメンバーの役割は単純にサイズをそろえる為に各構造体の末尾に加えられているように見えたのですが、如何でしょう? もしそうであれば(つまり使用されないメンバーであれば)、各構造体変数を定義した直後に構造体変数全体をスペースで初期化しておけば良い様に思われます。 そうすれば使用されないメンバーfillerはスペースで埋まったままファイルに書き込まれますし、読み出したときも同じ型の構造体変数に取り込めばスペースで埋まるでしょうし・・・。 想定されている使い方とは違いますか? 尚、fillerは末尾に加えられている、と書きましたが、初期化後書き換えられる可能性が無いメンバーでしたらどこに入っていてもOKですよね。
- itohh
- ベストアンサー率45% (210/459)
こんにちは。itohhといいます。 いっそのこと、初期化としてAとBのレコードをスペースクリアするっていうのは、NGですか? 例. A_REC a; memset( &a, ' ', sizeof(A_REC) ); // スペースでクリアする これならば、どこにfillerがあっても、OKだと思うんですけど。 はずしていたら、ごめんなさい。
補足
ご回答は、fillerまたはそれと同じようなメンバがどこにいくつあっても対応できる点がいいと思います。 ご回答はNo.2(#2)の方と近いですが、 私の質問の仕方ではまさしく、おっしゃるとおりの答えになると思います。 しかし、私が考えていることは、少し違います。 大きい目で見ると、 int piyopiyo(......) { ....... while(....) { レコードを作成するもとになるデータを取得する。 レコードのfillerメンバを空白で埋める。 レコードをファイルに書き込む。(エラーなら処理を中止し、-1をreturn) } 書き込んだレコード件数をreturnする。 } このような関数piyopiyoを作りたいのです。この関数piyopiyoは、A、Bどちらにでも使えるようにしたいのです。(実をいうと、本当は、A,B以外にもっと、ファイルがたくさんあるのです。) それで、質問で求めている関数は、 piyopiyoの中の「レコードのfillerメンバを空白で埋める。」 の部分の働きをするものです。 だから、 memset( &a, ' ', sizeof(A_REC) ); というように、aやA_RECというふうには、書きたくないのです。 質問の仕方が悪くて本当にすみません。m(__)m
- leaz024
- ベストアンサー率75% (398/526)
> fillerというメンバは必ず空白(スペース)で埋めなくてはいけません。 とありますが、これはA_RECやB_RECに含まれる、COM構造体のfillerも含まれますか?だとすると、toysmithさんのマクロは、さらに手を加えなければなりませんが・・・ まぁそれとは別に、もう少し安全な方法を。 構造体のサイズが違う点を利用して、関数で処理するようにしてみます。 まず、 void init_filler(void *pv, size_t size); というプロトタイプの、共用fillerメンバ初期化関数を作ります。 引数には、構造体変数のアドレスと、サイズを渡します。 これだけでは呼び出すのが面倒なので、呼び出しをマクロ化します。 #define INIT_FILLER(x) init_filler((x), sizeof(*(x))) xには、構造体変数のアドレスを指定します。 こうすると、間違って構造体変数そのものを渡してしまっても、構文エラーになります。 肝心の関数の実装は、次のような感じです。 void init_filler(void *pv, size_t size) { A_RECp pa; B_RECp pb; switch (size) { case sizeof(A_REC): pa = (A_RECp)pv; memset(pa->filler, ' ', sizeof(pa->filler)); break; case sizeof(B_REC): pb = (B_RECp)pv; memset(pb->filler, ' ', sizeof(pb->filler)); break; } } 一応、 init_filler(NULL, sizeof(A_REC)); などという意地悪な呼び出し方をしない限り、それなりに安全に動作します。 また先に言ったように com.filler もスペースで埋めなければならない場合は、それぞれ1行ずつ memset の行を加えてください。 それから > typedef struct { > char rec_kbn[1]; > char num[16]; > char filler[3]=" "; ←ここ! > } COM, *COMp; という記述はできません。 これはあくまで型の「宣言」であって、変数の「定義」ではないからです。 メンバの初期化は、構造体変数の宣言時に、初期化構文に従って行ってください。 ただし、" "(スペース3つ)では初期化できません。" "には、見えない '\0' が付いていることを忘れちゃだめですよ。
お礼
あと、なるべくなら、この関数の中に、 A_RECp,B_RECp のような特定の構造体に対応する記述は、本当はないほうがいいのですが... (C_RECとかD_RECとか、どんなレコードにでも対応するようにしたい。) それはできないんですよね.... (^^;
補足
>> fillerというメンバは必ず空白(スペース)で埋めなくてはいけません。 >とありますが、これはA_RECやB_RECに含まれる、COM構造体のfillerも含まれますか? 含まれます。 私がなるほどなあ、と思ったのは、 void init_filler(void *pv, size_t size) と仮引数pvがvoid * で宣言されていて、 pa = (A_RECp)pv; pb = (B_RECp)pv; とキャストしているところです。 ありがとうございました。 > それから >> typedef struct { >> char rec_kbn[1]; >> char num[16]; >> char filler[3]=" "; ←ここ! >> } COM, *COMp; >という記述はできません。 > これはあくまで型の「宣言」であって、変数の「定義」ではないからです。 わかりました。 /************************************************ ところで、文字配列の初期化に関することですけど。 本質問の場合は違うのかもしれませんが、 『新版 秘伝C言語問答ポインタ編』(柴田望洋 著)(ソフトバンク) という本(p64)に気になる記述が書いてあります。 「文字配列の初期値つき定義において char str[3]="ABC"; のように、配列の大きさと文字列の長さが等しい場合、 '\0'が付加されることなく初期化される。」 *************************************************/
- toysmith
- ベストアンサー率37% (570/1525)
> 型に依存しない処理をかけそうだな Cは本来「型に依存する言語」ですから型に依存しない処理はあまり書かないほうが… この手のマクロは逃げ道です。 逃げ道を用意しなければならない状況とは 「開発メンバーがC言語を良く理解できておらず、OJTしているひまも与えられない」 っていう異常な場合が多いでしょう。 それ以外の状況では「ちゃんと型を意識したプログラミング」を指導するのが王道かと思います。 で、#1の私の書いたマクロ、めちゃくちゃ間違ってました。 #define Fill(xx) \ ((sizeof(xx) == sizeof(void *) ?\ (memset((xx)->filler, sizeof((xx)->filler), ' ')) : (memset((xx).filler, sizeof((xx).filler), ' ')) これでは動きませんね。 #define Fill(xx) \ ((sizeof(xx) == sizeof(void *) ?\ (memset((void *)&(xx)->filler, ' ',sizeof((xx)->filler))) : (memset((void *)&(xx).filler, ' 'sizeof((xx).filler))) 条件を満たせば動くと思いますがかなり危ないマクロです。 2回も訂正してすみません。 見なおし不足でした。
補足
もしも誤解があるといけないので。 質問中の >どんな構造体(あるいは構造体のポインタ)を受け取っても、 というのは、 「構造体でも構造体のポインタでも、どっちにも対応できなくてはいけない」 という意味ではなく、 「どちらかに対応できていればいい」 という意味です。 質問が下手なのかもしれません。すみません。^^);
- toysmith
- ベストアンサー率37% (570/1525)
memsetの引数を間違えてました。 第2引数と第3引数が逆です。 ごめんなさい。
お礼
ありがとうございます。
- X4RR
- ベストアンサー率62% (5/8)
C言語の仕様では変数はstaticで宣言されてる場合を除き、特定の値で初期化されることはありません(参考URLの1.30を参照) で、この場合の初期化の方法としてはmemset関数あたりが有効ですね。 COM c; memset(&c, ' ', sizeof(COM)); /*構造体を空白で初期化*/ とか。
補足
fillerの初期化がどのようにされているかの話は、 typedef struct { ...... }A_REC, *A_RECp; っていうのが問題というより、 A_REC a_rec; という宣言がどこでされているか、staticがついているか、 によるという話ですね。 (参照URL1.30では、明示的に構造体の話は書いてないけれど。) ありがとうございました。
- toysmith
- ベストアンサー率37% (570/1525)
マクロを連動使ってみては? sizeof(struct ...)!=sizeof(void *)であることを条件とすれば…。 #define Fill(xx) \ ((sizeof(xx) == sizeof(void *) ?\ (memset((xx)->filler, sizeof((xx)->filler), ' ')) : (memset((xx).filler, sizeof((xx).filler), ' ')) C99ならtypeofを使ってもっとスマートに出来ると思いますが現行ANSI-Cだとマクロを使わないと難しいと思います。 C++だとコンストラクタでfillerを空白埋めできますね。class化しないといけないから多少ややこしいかも。 それよりも… memset()を呼ぶだけだから素直に memset(&p->filler, sizeof(p->filler), ' ') ; とか memset(&p.filler, sizeof(p.filler), ' ') ; って書いちゃいけないんですか? あと、構造体変数の初期値は確保方法と記憶クラス、初期化指定に依存します。 初期化指定無し: 静的に実体を確保確保した場合='\0'。 自動変数として実体を確保した場合=不定 初期化指定あり: 初期化指定に従う 動的確保した場合(標準関数を想定): malloc()の場合=不定 calloc()の場合='\0'
補足
早速ありがとうございます。 さすがなるほどなあと思ったのは、マクロを使えば、型に依存しない処理をかけそうだな、ということです。 Aに関する処理と、Bに関する処理をまとめて一つの関数にしたいのです。 よく考えさせていただきます。ありがとうございました。
お礼
みなさまがたへ。 みなさんありがとうございます。 読み返してみると、なんだか、私の知りたいことが私の質問にうまくあらわされていないようなので、 このまま、質問や補足を続けていても、答える側の方も混乱してくるかと思いますので、 まことにかってながら、この辺で締め切らせていただきます。 どの方のお答えも私の質問に対しては、正しいのだと思います。 ですから、ご回答されたみなさんに点を差し上げたいのですが、 そういうことにはいかないので、 できるだけ、わたしの知りたいこと(または それに近い方)に点を差し上げることにしました。 次に質問するときは、ちゃんと適切な質問を心がけます。 すっきりしない結末ですみません。