🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: fj.lang.c
Article #496

[Q]区切りなしデータを文字型でない配列に読むには?

#496
From: Tomoaki Chiba
Date: Fri, 20 Mar 1998 00:00
194 lines
8357 bytes

千葉@東北大 と申します。

fj.test 以外には初投稿ですので、不備がありましたら御指摘下さい。
また一部に、Subjectに示した一般的な問題でなく、自分の作業環境(あるいは
不適切な関数使用)に由来する?と思われる部分もあるのですがお許し下さい。

# おまけに180行ぐらいと長くなってしまいました。すみません。

ちなみに自分の作業環境は SPARCstation5 で SunOS Release 4.1.3-JLE1.1.3_U1
の gcc version 2.7.2.1 と、
パソコン(ハンドメイド?オーダーメイド?)で FreeBSD 2.2.2-RELEASE #0
の gcc version 2.7.2p を使用しています。

------------------------------------------------------------------------

Subjectが長いのはよろしくないと思って切ってしまいましたが、正確には

[Q]長くて区切り文字のない文字列(数字列)を、ファイルから、行単位で
   (一行丸ごとという意味)、文字型でない配列に、可能な限り簡潔に、
   読み込むには?

といったところです。
「可能な限り簡潔に」とは、ソースコードとしても短く、かつ、オンライン
マニュアルを読めて「はじめてのC」(技術評論社)程度にC言語の知識を
持ち合わせている(C++はほとんどわからない)人が、ソースプログラム
を読んで多くてもせいぜい10回程度(そのプログラムを)読み返すだけで理
解できるレベルと考えて下さい。


具体的に何をしたいかといいますと各行が

13202341795053000891325518391019052

のように数字のみで成り立っているデータを5桁の数字の並び、すなわち

13202 34179 50530 00891 32551 83910 19052

としてそれぞれを整数型(int)もしくは実数型(floatかdouble)の変数に入れた
いのです。ここで 00891 は 891 にというように先頭から続く 0 は取り去っ
た値を入れます。


もう一つ重要なのはこのデータの各行は 200~400×5 個位の数字から成り立
っているということです。すなわち行ごとに例えば Data[350] のような配列
に読み込むことを考えます。

-------------------------------------------------------------------

なぜこんなことを考えているかというと、最初は

int i;
FILE *fp;  /* データファイルのファイルポインタ */
char CharArray[6]; /* 5バイト分?のデータを一時的に読み込む配列 */
double DblData[350];  /* 実際にデータを格納する配列 */
for(i=0; i<350; i++) {
	fread(CharArray, 5, 1, fp);
	CharArray[6] = '\0';
	DblData[i] = atof(CharArray);
}

といったプログラムで上述のようなデータを読んでいました。

----------------------------------------------------------------------

ここで、ループを使って5文字分ずつ読んでいくのではなく、一行分ずつ
一気に読んでしまいたいという考えが浮かんできました。そこでまずは

char CharArray2D[350][5];
fread(CharArray2D, 5, 350, fp);
for(i=0; i<350; i++)
	DblData[i] = atof(CharArray2D[i]);

のようなものを考えました。ところが、atof系の関数の引数は"文字列"であり
空文字('\0')までを文字列とみなすので、

変数         中身
DblData[0]  13202341795053000891325518391019052
DblData[1]  341795053000891325518391019052
DblData[2]  5053000891325518391019052

のように引数の位置から後が全て代入されてしまいます。
CharArray2D ではなく CharArray1D[350*5] を使っても同じです。

DblData[i] = 10000 * ( CharArray2D[i][0] - '0' ) \
		    + 1000 * ( CharArray2D[i][1] - '0' ) \
		    +  100 * ( CharArray2D[i][2] - '0' ) \
		    +   10 * ( CharArray2D[i][3] - '0' ) \
		    +    1 * ( CharArray2D[i][4] - '0' );

のようにすれば(足し引き間違ってたらすみません)、atof系の関数は使わな
くてもよいのですが、これはとりあえず「可能な限り簡潔」ではないとさせて
下さい。
ちなみに fgets を使って1行丸ごと読む方法でも上と同様な「冗長さ」が生ま
れると思われます。

---------------------------------------------------------------------

そこで次にダイレクトに

FILE *fp;  /* データファイルのファイルポインタ */
double DblData[350];  /* 実際にデータを格納する配列 */
fread(DblData, 5, 350, fp);

などということはできないかと思ってやってみました(というかこれで
すんなりうまくいっていたらニュースには投稿していませんでしたが)。

---(以下、自分の作業環境依存の話?)-------------------------------

ところが、doubleやfloatには 0.0 より小さい(E-10~E-19のオーダー)値、
int には 750000000~950000000 くらいの大きな値が入ってしまいます。

fread(DblData, 5, 1, fp);
fread(DblData, sizeof(DblData[0]), 350, fp);
fread(DblData, sizeof(DblData[0]), 1, fp); /* これできちんと読み込め
                                                ないのはおかしい? */
のようにもしてみましたが、結果はいずれも同じでした。
よって、上の結果は自分の環境(OSやコンパイラ)に依存している可能性
もあります。
ちなみにfreadの返却値はいずれの場合も3つ目の引数に一致しています。

アドレス演算子&を用いて

fread(&DblData[0], sizeof(DblData[0]), 1, fp);

のようなことをしても同じ結果になります。

-------------------------------------------------------------------

ポインタを使って

double *DblDataP;
fread(DblDataP, 5, 1, fp);

のようなことをすると、SunOS では Segmentation fault します。デバッガ
(dbx)で調べると上の fread を呼び出した場所で

signal SEGV (segmentation violation) in memcpy at 0xef77aa2c
memcpy+0x17c:   stb     %o4, [%o0]

で止まります。
(デバッガも良く知らないので詳しくはわかりませんが、fread から memcpy
  を呼び出していて、その memcpy のところで領域のアドレス違反を侵して
  いるといったところでしょうか?)
FreeBSDではアドレス演算子を用いた時と結果は同じです。


さらに void *型や int型や int *型その他にキャストして当てずっぽう的に
試してみましたが、今度はFreeBSD で Bus error して memmove で領域違反?
と言われたり、SunOSでは上と同じ結果になったりで解決の手がかりにもなり
そうにありませんでした。

# どういうキャストをした中身がきちんとキャスト前の値を参照できるかに
# ついての知識は自分にはほとんどありません。

---(以上、自分の作業環境依存の話?)--------------------------------

fread でうまくいかないのは作業環境が原因のような気がしてきたので、
今度は fscanf を使えないかと考えてみました。
fscanf では フォーマット文字列(2つ目の引数)中で

"%5f%5f%5f"

のように読み込む「文字数」を指定して文字型以外の変数にも読み込めます。
フォーマット文字列も連結の繰り返しで簡潔に記述して作成することがなんと
かできそうです。
しかし長い一行を一気に読むことを考えた場合、3つ目の引数以降の、読み込
み先のアドレスを指定する部分はそれこそ

fscanf(fp, FORMAT, &DblData[0],&DblData[1],&DblData[2],.....

というのが &DblData[349]); まで続く記述がソースファイル中にそのまま横
たわっている姿しか思い浮かんできません。

# そもそも行の長さが最大何文字までとどこかで決まっていたような気もし
# ます。

-----------------------------------------------------------------------

以上が自分で考えて思考錯誤してみて出てきた結果です。


初心者にも理解できる、行データを丸ごと読み込む、簡潔で抜けるようなアイ
ディアが何かないものでしょうか。
自分のやり方では fread の使い方が変だというような御指摘もお待ちしてい
ます。

---------------------------------------------------------------
Name       : Tomoaki Chiba 千葉 智章
           : Inst.Geol.Paleon.Dep.Geo.Env.Sci.Tohoku.Univ
           : 東北大学理学部地圏環境科学科地質学古生物学教室4年
E-mail     : chiba@dges.tohoku.ac.jp
FAX        : 022-217-6634
Technical Details
Message-ID: <6esjd9$4t@sakunami.gw.tohoku.ac.jp>
Path: arc1-ca.pugleaf.net!unknown!not-for-mail

Show Full Headers
Complete Article Headers
X-Google-Language: Japanese,JIS
X-Google-Thread: 106434,8756f6f59b5f78c5,start
X-Google-Attributes: gid106434,public
From: chiba@dges.tohoku.ac.jp(Tomoaki Chiba)
Subject: [Q]区切りなしデータを文字型でない配列に読むには?
Date: 1998/03/20
Message-ID: <6esjd9$4t@sakunami.gw.tohoku.ac.jp>
X-Deja-AN: 335798249
Mime-Version: 1.0
Distribution: fj
Content-Type: text/plain; charset=ISO-2022-JP
Organization: Tohoku University, 980-77 Sendai JAPAN
Newsgroups: fj.lang.c