つくる 引数とバイナリ保存

章の道具を1本にまとめる

この章では、動的な表、コマンドライン引数、バイナリ保存、errno を1つずつ扱ってきました。今回はそれをまとめて、成績管理CLI を「起動するときに保存先を決めて、終わってもデータが残る」形にします。

作るものの流れは次のとおりです。

  1. argv[1] を保存先として受け取る。無ければ案内して終わる
  2. 標準入力から人数と学生を読み、Student の動的配列に入れる
  3. 保存先へ fwrite でまとめて書き出し、配列を解放する
  4. 同じファイルを開き直し、入っている件数を数えて読み戻す
  5. 一覧と平均を表示して、確保したものを解放する

保存先をプログラムの中に固定で書かないのがこの回の要点です。名前を外から渡せるようになると、クラスごとに別のファイルへ保存するといった使い分けが、プログラムを直さずにできます。

件数をファイル自身から求める

書いた側は件数を知っていますが、読む側は本来知りません。次に起動したとき「何件入っているか」を知る方法が要ります。1件の大きさが決まっているバイナリなら、ファイル全体の大きさを1件の大きさで割れば件数が出ます。

fseek(in, 0, SEEK_END); long bytes = ftell(in); fseek(in, 0, SEEK_SET); int count = (int)(bytes / (long)sizeof(Student));

fseek は読み書きの位置を動かす関数で、SEEK_END を指定すると末尾へ飛びます。そこで ftell を呼ぶと先頭からのバイト数、つまりファイル全体の大きさが分かります。数え終わったら SEEK_SET で先頭へ戻してから読み始めます。戻し忘れると末尾から読もうとして 0 件になり、しかもエラーにはならないので気付きにくい失敗です。

この割り算が成り立つのは、全部の件が同じ大きさで並んでいるからです。テキスト形式では1行の長さがまちまちなので同じ手は使えず、最後まで読んで数えるしかありません。バイナリの「決まった大きさで並ぶ」性質が効いている場面です。

解放するタイミングを分けて考える

書き出しに使った配列は fwrite が終われば役目を終えるので、そこで解放してかまいません。読み戻すための配列は、件数が分かってから改めて確保します。1つの変数を使い回さず、書く用と読む用を分けておくと、どちらがいつまで生きているのかがコードを読むだけで分かります。

途中で失敗して return するときも、そこまでに確保したものは解放してから帰ります。すぐ終わるプログラムなら OS が片付けてくれますが、この形をそのまま長く動くプログラムへ持ち込むと、そこがリークになります。

では、第6章の総まとめを書いてみましょう。

要件

  1. 引数が無いときは 保存先のファイル名を引数で指定してください の1行だけを出して終わる
  2. あるときは 保存先はseiseki.binです の形で argv[1] を出す
  3. 標準入力から人数と 名前 点数 を読み、Student の動的配列に入れる
  4. fwrite で書き出し 3件保存しました の形で出し、書き出し用の領域を解放する
  5. 開き直して fseek と ftell で件数を求め、3件読み戻しました の形で出す
  6. 佐藤 80点 の形で一覧を出し、最後に 平均は79.0点です の形で小数第1位まで出す

入出力例

main("3 佐藤 80 鈴木 92 高橋 65 ") → "保存先はseiseki.binです 3件保存しました 3件読み戻しました 佐藤 80鈴木 92高橋 65平均は79.0点です" main("2 田中 74 渡辺 88 ") → "保存先はkari.binです 2件保存しました 2件読み戻しました 田中 74渡辺 88平均は81.0点です" main("")"保存先のファイル名を引数で指定してください"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

つくる 引数とバイナリ保存

⌘S で保存