プロが教える店舗&オフィスのセキュリティ対策術

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){



A 回答 (9件)

>>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;
}

この回答への補足

ご返事有り難うございました。
参考のプログラムまで書いて頂いたので
とても勉強になりました。

参考のプログラムで
幾つか質問があるのですが…

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);
としたところ
何も表示されませんでした。

ファイルの中身は漢字なんですが
そこが問題なのでしょうか?
それとも別の原因があるのでしょうか?

面倒をお掛けしますが
よろしくお願いします。

補足日時:2006/08/15 16:06
    • good
    • 0

#7です


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

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

お礼日時:2006/08/21 10:33

構造体を fgets で読もうとしてるのがたぶん変。


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

この回答への補足

ご返事が遅れましてすいません。
お陰様で随分と理解を深めることが出来ました。

>そもそもファイルはテキストでしょうかバイナリでしょうか。
テキストです。

>テキストなら、構造体の要素 tiku や ken をそれぞれ読んで
>それを構造体に入れるといいのでは。
ということは、
shirousa01さんが回答して頂いたように
>while (fgets(buff,sizeof buff,fp) != NULL){
と、いったんバッファに蓄えて
>memcpy(&dou[i].tiku ,buff,10);
>memcpy(&dou[i].ken ,buff+10,8);
と、入れればよいと言うことでしょうか?

ご面倒かとは思いますが
ご教授して頂けたら幸いです。

補足日時:2006/08/18 16:30
    • good
    • 0

[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));
}

この回答への補足

ご返事が遅れましてすいません。
折角回答して頂いたので
なんとか理解しようと頑張ってみましたが
初心者の私には難しかったです。
ごめんなさい。
早くこのプログラムが理解できるように
勉強していきたいと思います。

一つだけ質問させて頂きたいのですが…。

>struct douken dou[NofLine];
>struct douken *douP;
普通に宣言した後に
ポインタで宣言していますよね。
これはどのような意味があるんですか?

ご面倒かとは思いますが
ご教授して頂けたら幸いです。

補足日時:2006/08/18 16:42
    • good
    • 0

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の問題がある場合もあります。
私がテストしたところ問題なく動作しました。

この回答への補足

ご返事が遅れましてすいません。
お陰様で随分と理解を深めることが出来ました。

幾つかご確認と質問があるのですが…。

>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);
ポインタにされていますよね?
実数(この言い方が正しいのか分かりませんが…)
でも、出力できました。
どうしてポインタにされたのでしょうか

ご面倒かとは思いますが
ご教授して頂けたら幸いです。

補足日時:2006/08/18 15:50
    • good
    • 0

> 32bit機というのは、どういうことですか?


現在主流のPCのことです。

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

この回答への補足

ご返事有り難うございました。
お陰様で良く理解することが出来ました。

>while (fgets(buffer,20,fp) != NULL){
と、するということですか?
その場合、
ここのサイズは必ず4の倍数になるということですよね?

補足日時:2006/08/15 11:11
    • good
    • 0

構造体ポインタをキャラクタポインタに型キャストすれば可能かとおもいますが、構造体を直接ファイルから扱うのはバグにつながりますよ?



まず、おそらく上記の構造体のサイズは18バイトにならない可能性が高いです。
32bit機の場合、4バイト単位で数値を扱う為
tiku:4バイト + 4バイト + 2バイト + 2バイト(ゴミ)
ken:4バイト + 4バイト
の20バイトになっていると思います。

この回答への補足

ご返事有り難うございました。
少し質問があるのですが…。

>32bit機の場合、4バイト単位で数値を扱う為
32bit機というのは、どういうことですか?

>tiku:4バイト + 4バイト + 2バイト + 2バイト(ゴミ)
どうして地区10バイトが
このようになるのでしょうか?

初歩的な質問かも知れませんが
ご教授して頂けたら幸いです。

補足日時:2006/08/15 10:14
    • good
    • 0

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です。
    • good
    • 0

fgets の使い方を見れば「どうしてエラーになるのか」はほとんど明らかだと思うんだけど,


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

この回答への補足

ご返事有り難うございました。
少し質問があるのですが…。

>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バイトということは
改行は含まないで指定すると言うことですか?

ご教授して頂けたら幸いです。

補足日時:2006/08/15 10:36
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています