• ベストアンサー

ファイルから読み込んだデータを構造体に格納できますか?

1レコード19バイトのファイルを 読み込む処理を行っています。 地区名10バイト 県名8バイト 改行1バイト このデータをdouken(構造体)に格納したいのですが >while (fgets(dou,19,fp) != NULL){ で、エラーになってしまいます。 どのようにしたら ファイルから読み込んだデータを 構造体に格納できますか? #include<stdio.h> #include <stdlib.h> struct douken { char tiku[10]; char ken[8]; } main(void){ FILE *fp; struct douken dou[100]; int i; fp = fopen("ex3.fil","rb"); if ( fp == 0 ){ printf("can't open\n"); exit(1); } while (fgets(dou,19,fp) != NULL){ ・ ・ ・

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

  • ベストアンサー
回答No.5

>>while (fgets(buffer,20,fp) != NULL){ >と、するということですか? >その場合、 >ここのサイズは必ず4の倍数になるということですよね? 構造体を直接扱うと、アーキテクスチャやコンパイラ依存してしまいます。 32bit機なら4byteですし、16bit機なら2byte。64bit機なら8byteです。 また、コンパイラの設定によってもどのように確保されるかまったく分からないのです。 一度バッファに蓄えてからmemcpyでコピーする方が安全ですし、可搬性があります。 C言語では\0を文字列の終端文字として使用しているので、10文字格納したいなら11byte確保する必要もあります。 簡単に修正してみました。 #include <stdio.h> #include <stdlib.h> #include <memory.h> typedef struct douken_ { char tiku[11]; char ken[9]; } douken; int main(void){ FILE *fp; douken dou [100]; char buff [18 /* douken */ + 1 /* LF(\n) */ + 1 /* \0 */]; int i; i = 0; fp = fopen("ex3.fil","rb"); if ( fp == 0 ){ printf("can't open\n"); exit(1); } // douを\0で埋める memset (dou , '\0' , sizeof dou); // 一度バッファに格納 while (fgets(buff,sizeof buff,fp) != NULL){ // memcpy関数でコピー memcpy(&dou[i],buff,10); memcpy(&dou[i],buff+10,8); // 構造体配列より大きなファイルを開いたときの配慮 if (i == 99) break; i++; } return 0; }

niiza
質問者

補足

ご返事有り難うございました。 参考のプログラムまで書いて頂いたので とても勉強になりました。 参考のプログラムで 幾つか質問があるのですが… 1. >typedef struct douken_ { >char tiku[11]; >char ken[9]; >} douken; どうして「typedef」というのを 入れたのかが分かりません。 本を読むと 構造体の場合は省略することが出来ると 書かれてあったのですが…。 どのような利点があるか 教えて下さい。 2. // douを\0で埋める memset (dou , '\0' , sizeof dou); どのような理由で douを\0で埋めるんですか? 3. 確認をするために 一番最後のところでprintfを書いてみました。 >if (i == 99) break; >i++; printf("%s",dou[i]); と、したところ 「nullnullnull・・・」 となりました。 printf("%s",dou[i].tiku); としたところ 何も表示されませんでした。 ファイルの中身は漢字なんですが そこが問題なのでしょうか? それとも別の原因があるのでしょうか? 面倒をお掛けしますが よろしくお願いします。

その他の回答 (8)

  • R32C
  • ベストアンサー率39% (115/290)
回答No.9

#7です -------------------------------------- douP = dou; while ((fgets((char*)douP++,sizeof(struct douken),fp)) != NULL) {} --------------------------- i=0; while ((fgets((char*)dou[i++],sizeof(struct douken),fp)) != NULL) {} --------------------------- でも、同じです。ポインタか配列かの違いだけです。 コンパイラの性能等にもよりますが、上記のポインタを使ったほうが 若干早いか、コードが小さくなる場合が多いです。

niiza
質問者

お礼

ご返事有り難うございました。 大変に参考になりました。

回答No.8

構造体を fgets で読もうとしてるのがたぶん変。 (構造体のサイズは、sizeof で分かるんだけど。) そもそもファイルはテキストでしょうかバイナリでしょうか。テキストなら、構造体の要素 tiku や ken をそれぞれ読んでそれを構造体に入れるといいのでは。

niiza
質問者

補足

ご返事が遅れましてすいません。 お陰様で随分と理解を深めることが出来ました。 >そもそもファイルはテキストでしょうかバイナリでしょうか。 テキストです。 >テキストなら、構造体の要素 tiku や ken をそれぞれ読んで >それを構造体に入れるといいのでは。 ということは、 shirousa01さんが回答して頂いたように >while (fgets(buff,sizeof buff,fp) != NULL){ と、いったんバッファに蓄えて >memcpy(&dou[i].tiku ,buff,10); >memcpy(&dou[i].ken ,buff+10,8); と、入れればよいと言うことでしょうか? ご面倒かとは思いますが ご教授して頂けたら幸いです。

  • R32C
  • ベストアンサー率39% (115/290)
回答No.7

[admin@opteron99] ~/gcctext $ less ex3.fil 12345abcdefgh^M一二三四五ABCD^M1234567890IJKLMNOP [admin@opteron99] ~/gcctext $ cat test5.c #include<stdio.h> #include <stdlib.h> #define NofLine 3 #define SizeofLineEnd 1 #if MSVCC #pragma pack(push,1) struct douken { char tiku[10]; char ken[8]; char crlfNULL[SizeofLineEnd+1]; } ; #pragma pack(pop) #endif #if GNUGCC struct douken { char tiku[10]; char ken[8]; char crlfNull[SizeofLineEnd+1]; } __attribute__((packed)); #endif int main(void){ FILE *fp; int i; struct douken dou[NofLine]; struct douken *douP; printf("struct douken SIZE = %d\n",sizeof(struct douken)); printf("dou SIZE = %d\n",sizeof(dou)); fp = fopen("ex3.fil","rb"); if ( fp == 0 ){ printf("can't open\n"); exit(1); } douP = dou; while ((fgets((char*)douP++,sizeof(struct douken),fp)) != NULL) {} douP = dou; for(i=0;i<NofLine;i++) { printf("Line%d:=",i+1); printf("tiku:%10.10s:",douP->tiku); printf("ken:%8.8s\n",douP->ken); douP++; } printf("end\n"); exit(0); } [admin@opteron99] ~/gcctext $ cc -DGNUGCC -g -Wall test5.c [admin@opteron99] ~/gcctext $ ./a.exe struct douken SIZE = 20 dou SIZE = 60 Line1:=tiku:12345:ken:abcdefgh Line2:=tiku:一二三四五:ken:ABCD Line3:=tiku:1234567890:ken:IJKLMNOP end [admin@opteron99] ~/gcctext $ ----------------------------------------------------------------------------- Cygwin ShitJIS 改行文字は、CRのみとしています。 直接構造体にとる方法は、独自プロトコルのデータのやり取り等で 比較的よく使われると思っています。(使っていました。) CPUやコンパイラの仕様によりアライメントに注意は必要ですが、 きっちりコンパイラに指定すれば、できないなんてことはないでしょう。 指摘事項について、ご確認されていないようですので回答しますと fgetsは、第2引数sizeよりも1バイト少ないデータをストリーム から読んで、第1引数のアドレスに書く、ただし、EOFまたは改行で 終わる。最後にNULLを書く。 ということなので、fgetsのwhileで取得するには、 tikuが10バイト、kenの8バイト、改行1文字(貴殿の指定により1文字としました。 Windowsなら2バイトですね)、NULL1バイトの計20バイトの領域が必要になります。 ですので、18を指定していたことと、領域の定義が不足していたことが貴殿のプログラム の問題のポイントだと思います。また他にもdouを100個の配列にしていますが まったくケアされていないこともバグのひとつですね。 上記プログラムもその2点とキャストをいれたぐらいです。 それからアライメントの指定を追加しているだけです。 それから、 改行およびNULLは飛ばして構造体に入れたいのであれば、fgetsでは20バイトのバッファ に取得し、18バイトを構造体にコピーするほうが、無駄なメモリを取らないので いい方法ですね。 その場合は、doukenから、crlfNullのメンバーを削除し、buffを定義(20byte)、 データ取得の部分を以下に置き換えるといいでしょう。 while ((fgets(buff,sizeof(buff),fp)) != NULL) { memcpy(douP++,buff,sizeof(struct douken)); }

niiza
質問者

補足

ご返事が遅れましてすいません。 折角回答して頂いたので なんとか理解しようと頑張ってみましたが 初心者の私には難しかったです。 ごめんなさい。 早くこのプログラムが理解できるように 勉強していきたいと思います。 一つだけ質問させて頂きたいのですが…。 >struct douken dou[NofLine]; >struct douken *douP; 普通に宣言した後に ポインタで宣言していますよね。 これはどのような意味があるんですか? ご面倒かとは思いますが ご教授して頂けたら幸いです。

回答No.6

1.どのような利点があるか 構造体を定義すると struct douken型 という型が定義されますが、好みの問題ですがstructを何度も書くのは面倒な為、構造体の名前を struct douken_型 として、それをtypedefで douken型 と再定義しています。 この定義の仕方はよく使われている方法です。 2.douを\0で埋めるんですか? 宣言しただけでは、構造体の中にゴミが詰まっています。 普段は意識しなくても問題ありませんが、ファイルを扱う場合や、構造体の場合、そのデータがバグにつながる可能性があるため、\0で初期化した方がバグが発生しにくいので、\0で初期化しています。 3.確認したところ、いくつか間違いがありました。 char buff [18 /* douken */ + 2 /* CrLf(\n\r) */ + 1 /* \0 */]; memset (dou , '\0' , sizeof dou); memset (buff, '\0' , sizeof buff); while (fgets(buff,sizeof buff,fp) != NULL){ memcpy(&dou[i].tiku ,buff,10); memcpy(&dou[i].ken ,buff+10,8); if (i == 99) break; i++; memset (buff, '\0' , sizeof buff); } データは漢字でも問題ありませんが、1文字2バイトになります。 また、encodingの問題がある場合もあります。 私がテストしたところ問題なく動作しました。

niiza
質問者

補足

ご返事が遅れましてすいません。 お陰様で随分と理解を深めることが出来ました。 幾つかご確認と質問があるのですが…。 >char buff [18 /* douken */ + 2 /* CrLf(\n\r) */ + 1 /* \0 */]; 18というのはtikuとkenを合わせたバイト数 +2というのはtikuとkenを合わせた改行文字数 +1というのは終端データ を確保していると考えて 宜しいのでしょうか? >memset (dou , '\0' , sizeof dou); というのは douのバイト数分 0を埋めていると言うことでしょうか? >memcpy(&dou[i].tiku ,buff,10); ポインタにされていますよね? 実数(この言い方が正しいのか分かりませんが…) でも、出力できました。 どうしてポインタにされたのでしょうか ご面倒かとは思いますが ご教授して頂けたら幸いです。

  • Trick--o--
  • ベストアンサー率20% (413/2034)
回答No.4

> 32bit機というのは、どういうことですか? 現在主流のPCのことです。 >> tiku:4バイト + 4バイト + 2バイト + 2バイト(ゴミ) > どうして地区10バイトが > このようになるのでしょうか? データを収納する「箱」が4バイトの大きさだから、です。 tikuに"1234567890"というデータが入っているとすると 実際には [1234][5678][90??] という4*3=12バイトの領域を使うことになります。 ??の部分が「ゴミ」です。

niiza
質問者

補足

ご返事有り難うございました。 お陰様で良く理解することが出来ました。 >while (fgets(buffer,20,fp) != NULL){ と、するということですか? その場合、 ここのサイズは必ず4の倍数になるということですよね?

回答No.3

構造体ポインタをキャラクタポインタに型キャストすれば可能かとおもいますが、構造体を直接ファイルから扱うのはバグにつながりますよ? まず、おそらく上記の構造体のサイズは18バイトにならない可能性が高いです。 32bit機の場合、4バイト単位で数値を扱う為 tiku:4バイト + 4バイト + 2バイト + 2バイト(ゴミ) ken:4バイト + 4バイト の20バイトになっていると思います。

niiza
質問者

補足

ご返事有り難うございました。 少し質問があるのですが…。 >32bit機の場合、4バイト単位で数値を扱う為 32bit機というのは、どういうことですか? >tiku:4バイト + 4バイト + 2バイト + 2バイト(ゴミ) どうして地区10バイトが このようになるのでしょうか? 初歩的な質問かも知れませんが ご教授して頂けたら幸いです。

  • R32C
  • ベストアンサー率39% (115/290)
回答No.2

fgetsの仕様をよく確認ください。18+(改行文字数)1+(\0の終端データ)1要求する必要があると思います。 デバッガにたよるのもあまりよくありませんが、デバッグされてはいかがでしょうか? 以下、そのままgdbを動作させた場合のログの一部です。 (gdb) run Breakpoint 1, main () at test4.c:24 (gdb) p dou $1 = {{tiku = "1234567890", ken = "abcdefgh"}, {tiku = "\000G, ken = "<\000\000\000\004\000\000"}, {tiku = "\004\000\000\000P, ken = "\021\000}, {tiku = ", {tiku = "\000\000, ken = "Uy (gdb) next (gdb) next Breakpoint 1, main () at test4.c:24 (gdb) p dou $2 = {{tiku = "\n\00034567890", ken = "abcdefgh"}, {tiku = "\000G, ken = "<\000\000\000\004\000\000"}, {tiku = "\004\000\000\000P, ken = "\000\000 (gdb) ex3.filは 234567890abcdefgh 1234567890ABCDEFGH 1234567890IJKLMNOP にしています。 環境は、linux で、gcc4.1.1です。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

fgets の使い方を見れば「どうしてエラーになるのか」はほとんど明らかだと思うんだけど, 1.fgets は第1引数に char * を要求するけど struct douken * は char * に変換できない というのが原因だよね. ただ, これは「コンパイラが文句を言ってくれる*たちのよい*エラー」であって, 実際には 2.struct douken は 18バイトかもしれないけど fgets で 19バイト読み込んでいる という「コンパイラがきっと文句を言わない*質の悪い*エラー」もまぎれこんでいるので注意.

niiza
質問者

補足

ご返事有り難うございました。 少し質問があるのですが…。 >1.fgets は第1引数に char * を要求するけど > struct douken * は char * に変換できない ということは、 このようにしていったんchar型に格納しないと いけないということですか? char buffer[19]; while (fgets(buffer,19,fp) != NULL){ >2.struct douken は 18バイトかもしれないけど fgets で >19バイト読み込んでいる 地区名10バイト 県名8バイト 改行1バイト というレコードフォーマットでしたので 19バイトと指定したのですが 18バイトということは 改行は含まないで指定すると言うことですか? ご教授して頂けたら幸いです。

関連するQ&A