つくる 引数とバイナリ保存
章の道具を1本にまとめる
この章では、動的な表、コマンドライン引数、バイナリ保存、errno を1つずつ扱ってきました。今回はそれをまとめて、成績管理CLI を「起動するときに保存先を決めて、終わってもデータが残る」形にします。
作るものの流れは次のとおりです。
argv[1]を保存先として受け取る。無ければ案内して終わる- 標準入力から人数と学生を読み、
Studentの動的配列に入れる - 保存先へ
fwriteでまとめて書き出し、配列を解放する - 同じファイルを開き直し、入っている件数を数えて読み戻す
- 一覧と平均を表示して、確保したものを解放する
保存先をプログラムの中に固定で書かないのがこの回の要点です。名前を外から渡せるようになると、クラスごとに別のファイルへ保存するといった使い分けが、プログラムを直さずにできます。
件数をファイル自身から求める
書いた側は件数を知っていますが、読む側は本来知りません。次に起動したとき「何件入っているか」を知る方法が要ります。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行だけを出して終わる
- あるときは 保存先はseiseki.binです の形で argv[1] を出す
- 標準入力から人数と 名前 点数 を読み、Student の動的配列に入れる
- fwrite で書き出し 3件保存しました の形で出し、書き出し用の領域を解放する
- 開き直して fseek と ftell で件数を求め、3件読み戻しました の形で出す
- 佐藤 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("") → "保存先のファイル名を引数で指定してください"