Dokusyo-nissi Bessitu 2006-04-27 φ(-_-) ■[lang]はじめての C う〜、ペースがなかなか元に戻らないヨ ... (;_;) リハビリも兼ねて、気になる page を訳してみます。 http://www.tofla.iconbar.com/tofla/c/cfy13 内容は、ファイルを操作する関数の多くがなぜ引数にポインタを用いるのか、という解 説。知らない関数もでてくるけど、あとで調べることにして ... この数ヵ月間、ファイル操作の基本を扱ってきましたが、一つ触れなかったところ があります。 もし、小さなファイルだけで済むなら、プログラムをスタートさせそこにデータを 入れて、実行時には何らかの必要な変更を加え、プログラムの終了時に全てのファ イルを再び保存することは、たぶん十分にうまくいくでしょう。 これは、ファイルのサイズが制限されていたからこそ、うまく動いたのです。でも 、もしその大きさが増えるようなことが起こったら ? ハードウェアにまだ余裕があったとしても、実行速度が少しずつ遅くなるのに気が つくと思います。 それがマルチタスク (並行処理) のプログラムだったら、他のプログラムを動かす ためのメモリが目に見えて減っていくのがわかるはずです。 ついには、ハードウェアのメモリを使いきってしまう段階にまでいきつき、これ以 上どんなデータも加えることができなくなってしまいます。 これらの問題を解決するには、ファイルやそのランダムな断片を不規則に leap さ せ、終了時に再度同じようにそれらを保存できるようにしないといけません。 C には、このことを考慮にいれた機能があります。でも、最良の結果を得るために は、file format にちょっと変更を加えるための準備をしておく必要があります。 2006-04-28 φ(-_-) ■[lang]はじめての C (続き) ファイルの構成 長さがすべて違った名前のリストを保存したいと考えたとき、もしこの作業を fscanf() を使ってやろうとすると、ファイルに含まれるどれか 1つの名前までたど り着くのは、容易な方法ではありません。というのも、そのファイルを調べ終わる までは、それぞれの名前がどれだけの長さかがわからないからです。 特定の記録を捜すためには、始めから通して検索しないとだめですから、大きなフ ァイルだとおそろしく時間がかかってしまいます。 これを仕上げるやり方は、特別なサイズをもつ区画 (block) に、そのデータを格納 し保存することです。 そうすれば、ある適切な補正によって、そのデータの確実な位置はファイルのどこ なのかが、上の記憶場所 (record) のサイズを掛けていくことで、確定できるので す。 2006-04-30 φ(-_-) ■[lang]はじめての C (続き) ファイルを format するために、それを助ける特別な関数がいくつか用意されてい ます。 fgetc(), fputc(), fgets(), fputs() はじめの 2つは文字を 1つ読み込み保持するもので、その他は全部の配列分を処理 します。 以下のコードで考えてみましょう。 char letter; int x; letter = fgetc(pointer); letter = 'Q'; x = fputc(letter, pointer); fgetc() は、ファイルにある、可変ポインタが指している次の文字を返します。ま たは、問題が起こったときには、マクロの EOF に等しい数値を 1つ返します。 fputc() は、なにもなければファイルに保持していた文字を返します。でも、さき ほどと同じく、なにか失敗したときには EOF を返します。 fgets() と fputs() は同じく行に沿って動きますが、1回の操作で文字列を読み込 み保持します。 int x; static char string[] = "Hello world"; x = fputs(string, pointer); このコードでは、"Hello world" という文字列を、ポインタが指す先のファイルに コピーします。関数が働いていれば、x の値は正になり、エラーだと EOF を返しま す。 この関数の大きく違っている点は、文字列の終わりに改行とナル文字を付け加えな いことです。もちろんそれが必要なら、そうするのは自由ですが。 char string[50]; int length = 50; fgets(string, length, pointer); 関数 fgets() は、ポインタで特定されたファイルから文字列を読むよう試みます。 そして改行文字か EOF まで着いたときか、または length - 1 個分の文字を読み終 わったときに止まります。 これらの条件の 1つにあっていれば、その後メモリ上の文字列はナル文字をつけて 終了します。この関数はファイルの終わりを見つけると、NULL を返します。 今まで学んだ他のものと組み合わせて、次に記す random access data のテクニッ クを用いて、簡潔な segment でデータを処理することができます。 なんだかヤヤコシクなってきたので、ちょっと manpage を写してみます。 int fgetc(FILE * stream); fgetc() は stream から次の文字を unsighned char として読み int にキャストと して返す。ファイルの終わりやエラーとなった場合は EOF を返す。 int fputc(int c, FILE *stream); fputc() は、キャラクタ c を unsigned char にキャストし、stream に書き込む。 int fputs(const char *s, FILE *stream); fputs() は、文字列 s を stream に書き込む。文字列に続く '\0' は出力しない。 (返り値) fputs() は成功すると負ではない数を、エラーが発生した場合は EOF を 返す。 char *fgets(char *s, int size, FILE *stream); fgets() は、size よりも 1文字少ない文字を stream から読み込み、s で示される バッファに書き込む。書き込みは EOF または改行文字を読み込んだ後終了する。改 行文字は読み込まれるとバッファに書き込まれる。 '\0' 文字がバッファの中の最 後の文字の後に 1文字書き込まれる。 (返り値) fgets() は成功すると s を返し、ファイルの終わりあるいはエラーの場 合 NULL を返す。 こうしてみると、C での文字列 string の扱いには、独特のクセがありますネ。 2006-05-02 φ(-_-) ■[lang]はじめての C (続き) ポジションの変更 random access に関する最初の関数が rewind() で、巻き戻すという名前が暗示す るように、与えられたポインタを取り込み、そのファイルの中のポジションをリセ ットします。ですので、次に読み込み操作をするときには、そのデータは最初のと ころから取り出します。 そのシンタックスは簡単です。 rewind(pointer); 次の 2つのコマンド ftell() と fseek() は関連しています。 前者はファイルの中の現在のポジションと一致した long の整数を返します。そし て、後者により他の地点へとジャンプさせることができます。 これが関数 ftell() の構成になります。 long position = 0L; FILE *pointer; pointer = fopen("file", "r"); position = ftell(position); 覚えておかないといけない重要なことは、ftell が long の整数を返すということ です。ですから、注意して変数を定義してください。 一度、あるポジションを保持すると、たぶんそのうちそこへ戻りたくなります。そ してその方法が、次の関数 fseek() による構成です。 返り値の整数が 0 なら、うまくいっています。もし 0 以外の値が返ったときはそ の要求は完了していません。 int x; x = fseek(pointer, position, SEEK_SET); ファイルでのポジションは後の 2つの引数 (パラメータ) によって指定されていて 、3つ目のパラメータを使って、その offset をどこから測定するかを選びます。 SEEK_SET では、offset をファイルの先端にとり、SEEK_CUR ではそれを現在のポジ ションから測定し、そして SEEK_END だと、offset はファイルの末尾に特定します 。 これら 3つのマクロがどこから現れたのか、驚いたでしょうけど、すべて stdio.h に定義されているのです。 ポジションを 20 byte 分、後ろにずらすのなら、こうします。 x = fseek(pointer, -20, SEEK_CUR); ファイルの末尾から前へ 10 byte 分ずらすのなら、こうです。 x = fseek(pointer, -10, SEEK_END); これらの関数を使えば、ファイルの中のポジションを前や後ろに移動させるどんな 組合せでも可能になります。 そして、注意深くファイルの構成を考えることで、データの断片をいつでも望むと ころへと入れることができるようになります。 先月、議論したファイル書式の更新と組み合わせて使うと、ハードウェアのディス ク容量がとても制限されている - ハードディスクをもってない人には申し訳ない - ファイルでも付け加えたり編集することができます。 さて、今のところファイルの操作についてはこれで十分でしょう。学習するための 一番の方法は、いつものように、自分でやってみることです。 これらの関数の manpage を写してみます。 void rewind(FILE *stream); rewind 関数は stream によって指定されたストリームにおける、ファイル位置表示 子 (file position indicator) の先頭にセットする。 long ftell(FILE *stream); ftell 関数は stream によって指定されたストリームにおける、ファイル位置表示 子の現時点での値を与える。 int fseek(FILE * stream, long offset, int whence); fseek 関数は stream によって指定されたストリームにおいてファイル位置表示子 をセットする。 新たな位置 (バイト単位) は whence で指定された位置に offset バイトを加える ことによって与えられる。 whence が SEEK_SET. SEEK_CUR, SEEK_END のどれかになっている場合は、それぞれ ファイルの先頭、現在の位置表示子、ファイルの末尾からのオフセットが取られる 。 (返り値) fseek は成功すると 0 を返す。 「ファイル書式の更新と組み合わせて」?? あとで、その page を捜さないと ... oLr 2006-05-05 φ(-_-) ■[lang]はじめての C この page だと思うんだけど、 http://www.tofla.iconbar.com/tofla/c/cfy11/ ファイルの取扱い 今月の tutorial の仕上げとして、ファイル出力先の変更はどうするかを、手短に 見てみましょう。 これらの操作を行う関数は、上で使った*1のにとても似ている fprintf() と fscanf() です。 こうした類似点は、C ではその入出力を同じ方法で扱うよう試みているという事実 から生じています。 そのため、スクリーンに表示するか、あるいはファイルへ保存するかどうかも、す べて同じメカニズムによるのです。 この 2つの特別な関数にいく前に、ファイルにデータを貯えるための fopen() と fclose() という関数について知る必要があります。 データを保存する前に、システムへは何らかの指示が与えられないといけません。 それによって、その情報をどこに貯えるかを知るからです。 逆に、ファイルの使用を止めたときには、すべての更新を完全にしものごとを片付 けるため、それを閉じなければなりません。 ファイルを開く際には、その開いたファイルと同一 number の書式 - handle が与 えられます。 これにより、handle を、そのファイル名で参照するかわりに、使用します。 FILE *handle; if ((handle = fopen("testfile", "w")) == 0) { printf("Test file counld not be opened.\n"); } FILE という変数の型は stdio.h で定義されています。そこにはシステムがデータ ファイルを参照するのに必要な情報を含んでいます - そして、この型の変数のポイ ンタを作成する必要があります。 ファイルが開けないときには、fopen() は 0 を返し、これによってプログラムをチ ェックするので、たとえ記録ディスクに問題が起きたとしても、上手に正常に戻す ことができます。 fopen() のステートメントには、2つのテキストパラメータをとっており、1つ目は そのファイル名、2つ目のそれでファイルに必要なアクセスは何かを決めています。 上の例では、ファイル名が testfile、アクセスは "w" という書き込み命令用のコ マンドになっています。 アクセスや、同じく読み取り、書き込みにはいくつかの方法があり、また書き込む ときには元のファイルの末尾につけ加えます。 また、テキストに変換しない、機械語で貯えられた数値をもつバイナリモードもあ ります。 いったん、ファイルを終了させたら、必ず fclose() で閉じるようにしてください 。 if (fclose(handle) != 0) { printf("File couldn't be closed.\n"); } ファイルが正しく閉じられれば、同じく関数 fclose は 0 を返すので、ここでのチ ェックは大事です。 さて、思いどおりにファイルの開閉はできるようになりましたが、実はまだ、少し だけ確実ではありません。 次の月では、入出力のルーティンをいくつかつけ加えて補うことにしましょう。 ■[diary]web archive Unsigned character http://www.dwheeler.com/blog/2006/03/28/#unsigned-char C に関連したところをちょっと訳してみた。 技術的な基礎から始めましょう。 C には char 型が含まれていて、通常 8 ビット 文字を格納し使用します。国際化したプログラムの多くは、テキストに UTF-8 を使 ってエンコードしているので、ユーザが見る文字は char の値の連続 (sequence) として格納されています。しかし、国際化したプログラムでさえしばしばテキスト がある 1つの char 型で格納されています。 C standard には、はっきりと char を signed あるいは unsigned にすることがで きると述べてあります (信じられませんか? では、ISO/IEC 9899:1999、セクション 6.2.5、パラグラフ 15 の 2番目のセンテンスを見るように、そうそこです)。多く の platform (たとえば Linux に代表される) で、char 型は signed になっていま す。問題は、ソフトウェアの開発者が char 型が unsigned であることを、しばし ばまちがって考えているか、signed 文字の分岐 (ramification) をわかっていない ことです。この思い違いは、時とともにより一般的になっています。なぜなら、他 の C に似た多くの言語 (Java や C# のような) が、必要上 unsigned と定義して いるか、あるいは、いくらかはたいしたことではないとしているからです。最悪の 場合、この思い違いはまっすぐセキュリティでの弱点へと導かれます。 singed 文字をともなうシステムでは、さまざまな種類の奇妙な事態を、発生させる ことができます。例えば、文字の 0xFF は、 C や C++ の拡張されたルールにより 、それと等しいものとして整数の -1 に一致 (match) するでしょう。そして、この ことはすぐにセキュリティの欠陥をつくりだすことができます。なぜなら、-1 はふ つう、多くの開発者が char では起こらないと推定している、番兵の値だからです 。 Sendmail のよく知られたセキュリティの欠陥の 1つは、まさにこの問題から起 こっています (詳細は、US-CERT #897604 と Michal Zalewski による投稿を参照) 。 (追記) 訳文を少しだけ訂正。 *1: printf() と scanf() 2006-05-10 φ(-_-) ■[lang]はじめての C あいかわらずリハビリちゅー ... http://www.tofla.iconbar.com/tofla/c/cfy12/ 挿入と保存 先月はファイルを開閉するテクニックについて見てきましたが、それを使って何か をするという具合にはいきませんでした。 先へすすむ前に、新しく 2つのルーティンを紹介しないといけません。でも、C の 背後にあるちょっとした考え方により、それらは前に見てきた関数によく似た特徴 をもっています。 C では、すべての入出力をさまざまなファイルとして扱っているのです。それで、 スクリーンやプリンタあるいは磁気ディスクであろうと、おなじ一般的な方法で書 き込むことができます。 printf() は自動的に stdout と呼ばれる「ファイル」を出力先として送り込みます 。また、scanf() は stdin からの入力を取り込みます。 これらは通常それぞれスクリーン上にまたキーボードから転送されます - もし誰か がパソコンにそっと近づいて書き直さないかぎりは、ですけど。 これらの「ファイル」は高い頻度で使われるため stdio.h ライブラリに標準で用意 されていて、いつでも開いた状態にあります。磁気ディスクに書き込む場合は、使 用できるようにする前に、ファイルをつくっておく必要があります。このやり方は 先月実行しました。 printf() や scanf() の転送先を変更するのは簡単にはできないので、他にパラメ ータをとる新しい関数が必要になります - それでその情報をどこに格納するのかを 指定するのです。 最初に fprintf() を見てみましょう。これは printf() と同じパラメータに加え、 特別な転送用パラメータをとっています。 FILE *pointer; char *name = "filename"; pointer = fopen(name, "w"); fprintf("pointer, "Writing to file: %s\n", name); fclose(pointer); 2006-05-13 φ(-_-) ■[lang]はじめての C (続き) 先月、ファイルにアクセスすることができるモードについて簡単に触れましたが、 使いたいときどのモードを特定するのかということは、それを説明するスペースが なくなってしまいました。 最初に、ファイルを読むのか、書き込むのかあるいはその最後へデータを付け加え るのかを決めないといけません。これらの動作は、それぞれ小文字の r, w, a によ って与えられます。 しかし、これだけではまだ少し制限があります。というのも、いくつかのケースで は、同時に 2つの操作を実行したいこともあるからです。 こうした処理を与える特別なモード - 更新 (update) と呼びます - が用意されて いて、それを使いたい場合には、モード選択のときに + の記号を加えておきます。 最後に、出力時には、テキストまたはバイナリ形式のどちらかを指定することがで きます - この 2つの違いは、テキストモードは表示可能な文字のサブセット (subset) だけからなっていて、数値はそれらテキスト表記内に格納されます。 バイナリモードでは、すべての ASCII 文字の値域 (range) で保存することが可能 で、数値はハードウェアのインターナル形式 (internal format) として格納されま す。もし、このバイナリモードを使いたいときには、モードの綴りに b を入れてく ださい。 注意する点として、r+ モードでは、更新時に実際にあるファイルを開くということ と、w+ モードではファイルが作成されるので、もし同じ名前のファイルがすでにあ ると、それを消してしまう、ということです。 2006-05-14 φ(-_-) ■[lang]はじめての C (続き) さて、3つの数値を扱うためのコードだと、次のように表します。 fprintf(pointer, "%d %f %d\n", var1 var2 var3); ここでは、1つの int、1つの float、もう 1つの int を、ファイルを指すポインタ に続けて取っています。大事なのは、数値を空白の文字で離してあることです - で ないと、数値を読み返すときにそれが区別できないからです。 読み込みを行うには、次の行のように使ってください。 fscanf(pointer, "%d %f %d", &var1, &var2, &var3); fscanf() は、必要なら、実際に整数値を 1つ返し、それはデータをうまく読み込ん だときの数値と一致します。 上の例では、3 というデータ数が戻ります。もし読み込みのときに問題が起きると 、fscanf() は 3 より小さな値を戻します。 この方法を使うときのコマンドの書式は、次に見るようになります。 int n; n = fscanf(pointer, "%d, %f", &var1, &var2); データを読む際には、ファイルの終わりまで届いたかどうかをチェックできるよう にすることが重要です。そして、そのための関数 feof() が用意されています。 それはファイルの終わりに届いたときは 0 以外の、またそうでない場合には 0 の 値を返します。 以下のコード断片では、ポイント先の、配列を使って保存しているファイルから、 整数の列 (line) を読んでいます。 このループは、プログラムがファイルの終わりを見つけるまで続きます。 int loop = 0; while (feof(handle) == 0) { fscanf(handle, "%d", &(array[loop]); loop++; } 今まで実行したファイルの取扱いはその順序通りのもので (sequential) あって、 ファイルを使っているときにはジャンプさせることはできませんでした。 しかし、C にはランダムアクセス (random access) のための機能が用意されていて 、使っているファイルが十分に大きく、メモリ上で一度にその全部を扱う余裕のな い場合には、特に役にたちます。 次回は、そのことを調べて、メモリ上の手強いデータを扱ういくつかの進んだ方法 に触れてみましょう。 で、こちらに続くわけです。 http://d.hatena.ne.jp/sekiyo/20060502 2006-09-02 φ(-_-) ■[lang]はじめての C 先月もチラッと触れたけど、C を学ぶ上でネックとなるのは - ポインタは別にして - データをプログラムにどう取り込むか、ということです。 で、やっとわかった↓ UNIX (の データ) にはテキストモードしかない。 これだけです ...oLr 「基礎C言語」(isbn:4000078569) に住所録のプログラムがあるので、それを少しいじっ てみます (p275-278)。 使う関数は fwrite (fread)。man page は次のとおり。 名前 fread, fwrite - バイナリストリームの入出力 書式 #include size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 説明 fread 関数は stream ポインタで 指定された ストリームから nmemb 個の データ を 読み込み、ptr で 与えられた 場所に 格納する。 個々の データは size バイトの 長さを 持つ。 fwrite 関数は ptr で 指定された 場所から 得た nmemb 個の データを、stream ポインタで 指定された ストリームに 書き込む。 個々の データは size バイト の 長さを 持つ。 返り値 fread と fwrite は 読み書きに 成功した 要素の 個数を 返す。 エラーが 生 じた 場合や、end-of-file (ファイルの最後) に 達した 場合、返り値は 指定 した 個数よりも より 小さい 値 (または ゼロ) となる。 fread は end-of-file と エラーを 区別しないので、どちらが 生じたかを 判断するためには、呼び出し側で feof(3) と ferror(3) とを 使用しなければ ならない。 早速、プログラムを組み立てていきます。データは構造体の配列に入れています。 /* addbook.h */ #define MAX_NAME 50 + 1 #define MAX_PC 12 + 1 #define MAX_ADDR 100 + 1 #define MAX_PHN 12 + 1 #define MAX_PSN 300 + 1 typedef struct { char name[MAX_NAME]; char postal_code[MAX_PC]; char address[MAX_ADDR]; char phone[MAX_PHN]; } PERSON; /* addbook.c */ #include #include "addbook.h" int main(int argc, char **argv) { PERSON add_book[MAX_PSN]; int c; FILE *fp; if (argc != 2) { fprintf(stderr, "Usage: ./addbook [file]\n"); exit(1); } if ((fp = fopen(argv[1], "wb")) == NULL) { fprintf(stderr, "Can't open %s.\n", argv[1]); exit(2); } puts("Press [ctrl + d] at the time of finish."); for (c = 1; c < MAX_PSN; c++) { puts("Enter the name."); if (fgets(add_book[c].name, MAX_NAME, stdin) == 0) break; else { puts("Now enter the postal code."); fgets(add_book[c].postal_code, MAX_PC, stdin); puts("Now enter the address."); fgets(add_book[c].address, MAX_ADDR, stdin); puts("Now enter the phone number."); fgets(add_book[c].phone, MAX_PHN, stdin); getchar(); } } if (c >= MAX_PSN) fprintf(stderr, "The entry is over.\n"); /* heap_sort(add_book, c - 1) */ fwrite(add_book + 1, sizeof(PERSON), c - 1, fp); fclose(fp); return 0; } このプログラムはまだ完成していません。このままだと、データを上書きしてしまうの と、途中でコメントアウトしている、 /* heap_sort(add_book, c - 1); */ の部分の関数ができてないからです。でも、コンパイルすればちゃんと? 動きますが ... $ cc -o addbook addbook.c $ ./addbook myaddbook Press [ctrl + d] at the time of finish Enter the name. NANNO nanibei Now enter the postal code. 111-1111 Now enter the address. 1-1-1 Nantoka, Kantoka Now enter the phone. 111-1111-1111 Enter the name. (<- ctrl + d) $ cat myaddbook NANNO nanibei 111-1111 1-1-1 Nantoka, Kantoka 111-1111-1111 $ 関数 fwrite, fread を使えば、構造体などのデータをファイルに書き入れたり読みだし たりができることと、(まだしてませんが) ファイルが開いた状態で、さまざまなアルゴ リズムを使ってデータをいじることができる、ということですネ。 (追記) コードを一部訂正 (06/09/27) 2006-09-08 φ(-_-) ■[lang]はじめての C 関数 fopen の man page を見ると、 a+ - 読み取りおよび追加 (ファイルの最後に書き込む) のために開く。ファイルが 存在していない場合には新たに作成する。ストリームはファイルの最後に追加され る。 mode 文字列には文字 "b" を追加指定することができる。これは上述のうちの 2文 字からなる文字列では、最後の文字としても、あるいは 2つの文字の間にも指定で きる。これは ANSI X3.159.1989 ("ANSI C") との互換性のためだけに用意されたも のであり、関数の実行に対してはいかなる影響も持たない。すなわち、Linux を含 む全ての POSIX 準拠システムでは、この "b" は無視される。(その他のシステムで はテキストファイルとバイナリファイルを別々に扱うものもあるので、もしバイナ リファイルの入出力を行い、そのプログラムが非 UNIX 環境へ移植されると予測す るなら、"b" を付けておくのは良い考えである) ファイルを追加モード (mode の最初の文字を a にする) で開くと、このストリー ムに対する書き込み操作は (先に fseek(stream, 0, SEEK_END); 呼び出しを実行したかのように) ファイルの末尾に対して行われる。 とあるので、addbook.c の該当個所を次のように書き換えた。 if ((fp = fopen(argv[1], "a+b")) == NULL) { fprintf(stderr, "Can't open %s.\n". argv[1]); exit(2); } なんとかこれで、データの上書きは防げそう ... 2006-09-10 φ(-_-) ■[lang]はじめての C データを追加できるのがわかったので、次は実際にファイルにどれだけの数のデータが あるか調べてみます。ここでは、関数 fseek と ftell を使います。 man page は次の とおり、 名前 fseek, ftell - ストリームの 位置を 変更する 書式 #include int fseek(FILE *stream, long offset, int whence); long ftell(FILE *stream); 説明 fseek 関数は stream によって 指定された ストリームにおいて、 ファイル位置表示子 (file position indicator) を セットする。 新たな 位置 (バイト単位) は whence で 指定された 位置に offset バイトを 加えることによって 与えられる。 whence が SEEK_SET, SEEK_CUR, SEEK_END の どれかに なっている場合は、 それぞれ ファイルの 先頭、現在の 位置表示子、ファイルの 末尾 からの オフセットが 取られる。 fseek 関数の 呼び出しが 成功 すると、ストリームの end-of-file 表示子は クリアされ、それ までに ungetc(3) 関数で 戻した データは なかったことになる。 ftell 関数は stream によって 指定された ストリームにおける、 ファイル位置表示子の 現時点での 値を 与える。 返り値 fseek は 成功すると 0 を 返す。 ftell は 現在の オフセットを 返す。 コードは、 #define SEEK_END 2 long last; int record; fseek(fp, 0L, SEEK_END); last = ftell(fp); record = (int)last / sizeof(PERSON); fprintf(stdout, "Record: %d item%s.\n", record, record == 1 ? "" : "s"); fseek で fp (file positin) をファイルの最後に移動、次にファイルの先頭から fp ま での値 = バイト数 (long) を数えます。 最後に、バイト数を構造体の配列 PERSON のサイズで割って、データの数を求めていま す (last が long 型なので、キャストで int に変更)。 (追記) 改訂したプログラムがこちら↓*1 /* addbook.c */ #include #include "add_book.h" #define SEEK_END 2 int main(int argc, char **argv) { PERSON add_book[MAX_PSN]; int c; long last; int record; FILE *fp; if (argc != 2) { fprintf(stderr, "Usage: ./addbook [file]\n"); exit(1); } if ((fp = fopen(argv[1], "a+b")) == NULL) { fprintf(stderr, "Can't open %s.\n", argv[1]); exit(2); } puts ("Press [ctrl + d] at the time of finish."); for (c = 1; c < MAX_PSN; c++) { puts("Enter the name."); if (fgets(add_book[c].name, MAX_NAME, stdin) == 0) break; else { puts("Now enter the postal code."); fgets(add_book[c].postal_code, MAX_PC, stdin); puts("Now enter the address."); fgets(add_book[c].address, MAX_ADDR, stdin); puts("Now enter the phone number."); fgets(add_book[c].phone, MAX_PHN, stdin); getchar(); } } if (c >= MAX_PSN) fprintf(stderr, "The entry is over.\n"); fwrite(add_book + 1, sizeof(PERSON), c - 1, fp); fseek(fp, 0L, SEEK_END); last = ftell(fp); record = (int)last / sizeof(PERSON); fprintf(stdout, "Record: %d item%s.\n", record, record == 1 ? "" : "s"); fclose(fp); return 0; } (追記) コードを一部訂正 (06/09/27) *1:06.09.13 2006-09-13 φ(-_-) ■[lang]はじめての C fseek の man page ではファイル位置表示子 "file position indicator" と呼ばれてい ますが、K&R 2nd の UNIX システム・コールの章を見ると、ファイル記述子 "file descriptor" として説明されています。 In the most general case, before you read and write a file, you must inform the system of your intent to do so, a process called opening the file. If you are going to write on a file it may also be necessary to create it or to discard its previous contents. The system checks your right to do so (Does the files exist? Do you have permission to access it?) and if all is well, returns to the program a small non-negative integer called a file descriptor. Whenever input or output is to be done on the file, the file descriptor is used instead of the name to identify the file. ... All information about an open file is maintained by the system; the user program refers to the file only by the file descriptor. 訳してみると、 最も一般的なケースでは、ファイルを読み書きする前に、そうした目的をシステム に知らせなければいけない。そのプロセスをファイルをオープンするという。 1つ のファイルに書き込もうとするには、同じく (ファイルを) 作成したりその以前の ファイルを削除する必要がでてくるだろう。システムは (ユーザが) それを行なう 権限をもっているか (ファイルは存在するのか? それにアクセスする許可はあるの か?) をチェックし、条件が合えばファイル記述子 (file descriptor) という 1つ の小さな正の整数をプログラムに渡す。 ファイルの出入力が行なわれる際には、このファイル記述子がファイルを特定して いるその名前に代わって使われる。... オープンしたファイルについての全ての情 報はシステムによって保持されていて、ユーザのプログラムはファイル記述子を通 してのみそのファイルを参照している。 (p207) プログラムでファイルを扱うには、オペレーティング・システム (OS) についても - 少 しだけ - わかっておく必要があるみたいですネ。 ファイル記述子 (fd) とファイル・ポインタ (fp) との違いについても、同じくシステ ム・コールの章で説明されていました。 2006-09-15 φ(-_-) ■[lang]はじめての C では、ファイル・ポインタとは何かというと、 Recall that files in the standard library are described by file pointers rather than file descriptors. A file pointer is a pointer to a structure that contains several pieces of information about the file; a pointer to a buffer, so the file can be read in large chunks; a count of the number of characters left in the buffer; a pointer to the next character in the buffer; the file descriptor; and flags describing read/write mode, error status, etc. 訳してみると、 標準ライブラリにおけるファイルの呼び出しでは、ファイル記述子 (fd) よりむし ろファイル・ポインタ (fp) によって記述されている。ファイル・ポインタは、そ のファイルに関する数個の情報を含む構造体へのポインタであり、1つの buffer を 1つのポインタで指すことで、ファイルを大きなカタマリとして読み取ることができ る。 (構造体のメンバに) buffer に置かれた文字*1数のカウント、buffer 内の次 の文字の位置を指すポインタ、ファイル記述子をもっており、(他に) read/write mode、error status 等々を記述した flags (共用体) がある。 (p214) 標準ライブラリ - この場合、ヘッダファイルの ですが - をインクルードす ることで、ファイル記述子の代わりにファイル・ポインタ - FILE * - を使って、ファ イルを扱えるようにしているわけです。 関数の fseek や ftell で、ファイル記述子 (fd) でなくファイル位置表示子 (fp) と いう名前が使われているのは、こうした設計上の整合性を保つためかもしれない ... (同じ fp という表示なので、ちょっと混乱しますが) *1:数字や記号を含む 2007-09-01 φ(-_-) ■[lang]はじめての C 昨年の 9月からそのままになってたアドレスブックの code をなんとか動かせるように してみた。まちがってる可能性もあるけど ... /* addbook.c */ #include #include "addbook.h" int main(int argc, char **argv) { PERSON add_book[MAX_PSN]; int c; long last; int record; FILE *fp; if (argc != 2) { fprintf(stderr, "Usage: ./add_book [file]\n"); exit(1); } if ((fp = fopen(argv[1], "a+b")) == NULL) { fprintf(stderr, "Can't open %s.\n", argv[1]); exit(2); } puts ("Press [ctrl + d] at the time of finish."); for (c = 0; c < MAX_PSN; c++) { puts("Enter the name."); if (fgets(add_book[c].name, MAX_NAME, stdin) == 0) break; else { puts("Now enter the postal code."); fgets(add_book[c].postal_code, MAX_PC, stdin); puts("Now enter the address."); fgets(add_book[c].address, MAX_ADDR, stdin); puts("Now enter the phone number."); fgets(add_book[c].phone, MAX_PHN, stdin); } } rewind(fp); heapsort(add_book, c); fwrite(add_book, sizeof(PERSON), c, fp); fclose(fp); return 0; } /* heapsort.c */ #include #include "addbook.h" void swap(); void shift(); void heapsort(PERSON a[], int n) { int left, right; left = n / 2 + 1; right = n; while (left > 1) shift(a, --left, right); while (right > 1) { swap(a, 1, right); shift(a, 1, --right); } } void swap(PERSON a[], int i, int j) { PERSON tmp; tmp = a[i]; a[i] = a[j]; a[j] = tmp; } void shift(PERSON a[], int left, int right) { int i, j; PERSON tmp; i = left; j = 2 * i; tmp = a[left]; while (j <= right) { if (j < right) if (strcmp(a[j].name, a[j + 1].name) < 0) j++; if (strcmp(tmp.name, a[j].name) >= 0) break; a[i] = a[j]; i = j; j = 2 * i; } a[i] = tmp; } /* findphn.c */ #include #include #include "addbook.h" #define SEEK_SET 0 #define SEEK_END 2 char * b_search(); int main(int argc, char **argv) { char name[MAX_NAME + 1]; char *phone; long last; int record; FILE *fp; if (argc != 2) { fprintf(stderr, "Usage; ./findphn [file]\n"); exit(1); } if ((fp = fopen(argv[1], "rb")) == NULL) { fprintf(stderr, "Can't open %s.\n", argv[1]); exit(2); } fseek(fp, 0L, SEEK_END); last = ftell(fp); record = (int)last / sizeof(PERSON); printf("record = %d\n", record); puts("Enter the name."); fgets(name, MAX_NAME, stdin); if ((phone = b_search(name, fp, record)) != NULL) fprintf(stdout, "%s <- %s", phone, name); else fprintf(stdout, "Can't find %s.", name); fclose(fp); } char *b_search(char *x, FILE *fp, int n) { int low, high, mid; static PERSON who; low = 0; high = n; while (low <= high) { mid = (low + high) / 2; fseek(fp, (mid - 1) * sizeof(PERSON), SEEK_SET); fread(&who, sizeof(PERSON), 1, fp); if (strcmp(x, who.name) > 0) low = mid + 1; else return who.phone; } return NULL; } /* addbook.h */ #define MAX_NAME 50 + 1 #define MAX_PC 12 + 1 #define MAX_ADDR 100 + 1 #define MAX_PHN 12 + 1 #define MAX_PSN 300 + 1 typedef struct { char name[MAX_NAME]; char postal_code[MAX_PC]; char address[MAX_ADDR]; char phone[MAX_PHN]; } PERSON; void heapsort(); $ cc -c addbook.c heapsort.c $ cc -o addbook addbook.o heapsort.o $ cc -o findphn findphn.c heapsort.c と findphn.c を説明しようと思ったけど、すでにチカラ尽きました o ...Lr