つくる 名前入力の安全化
章の道具を1本の入力処理にまとめる
第3章でやってきたことを並べ直します。終端まで数える my_strlen、1バイトずつ写す my_strcpy、器の広さを渡してあふれさせない safe_copy、カンマで切る分解、長さぶんだけ確保する my_strdup。この5つは、実はどれも「外から来た1行を安全に取り込む」という1つの仕事の部品です。今回はそれを組み上げます。
入力は 名前,点数 の行です。外から来るものなので、次のどれが来ても壊れてはいけません。
- 想定より長い名前
- カンマが無い行
- 空行
切り詰めてから確保する順序
今回の要点は、safe_copy と my_strdup を この順番で 使うところです。
char fixed[NAME_MAX + 1];
cut[n] = safe_copy(fixed, NAME_MAX + 1, line);
names[n] = dup_name(fixed);まず幅の決まった一時置き場 fixed に、あふれない形で受けます。ここで長さの上限が確定します。そのあと、確定した長さぶんだけヒープに確保して写します。順番が逆だと意味がありません。長さ制限をかける前に確保してしまうと、いくらでも長い名前をそのまま受け入れることになり、上限を設けた意味が消えます。
一時置き場が固定長でよいのは、そこには決して上限を超えて書き込まないからです。固定長そのものが悪いのではなく、広さを見ずに書き込むことが悪い、という区別がここではっきりします。
捨てた情報を黙って捨てない
safe_copy は切り詰めたかどうかを返します。この戻り値を捨ててしまうと、Yamamotokurosawa が Yamamoto になったことを誰も知らないまま一覧に載ります。あとから別人と区別が付かなくなっても、原因を追う手掛かりが残りません。今回は切り詰めた行に印を出して、データが加工されたことを表に出します。
壊れた行は読み飛ばす
カンマが無い行や空行が来たときに落ちる作りだと、ファイル1行の乱れでプログラム全体が止まります。分解の前に line[pos] が '\0' かどうかを見て、区切りが見つからなければその行は数えずに次へ進みます。人数 n を増やさないので、平均の計算にも影響しません。
最後に、確保した回数と解放した回数を出して、数が合っていることを確かめます。第2章で覚えた数え方が、そのまま今回の検算になります。読み飛ばした行では dup_name を呼んでいないので、確保の回数は一覧に出た人数と一致するはずです。ここがずれたら、読み飛ばしの位置か人数の数え方のどちらかが間違っています。
平均の割り算
人数で割るときは、整数どうしの割り算にならないよう気をつけます。total / n はどちらも int なので小数が切り捨てられます。(double)total / n と片方を実数にしてから割り、%.1f で桁を決めて出します。入門でやった話ですが、動的メモリの処理に気を取られていると落としやすいところです。
では、第3章の総まとめとして、あふれない名前入力を書き上げましょう。
要件
- fgets で1行ずつ読み、行末の改行を取り除く
- 空行と、カンマが無い行は読み飛ばして人数に数えない
- 名前は safe_copy で8文字までに受けてから、dup_name で必要な長さだけ確保して保持する
- 一覧は Tanaka 85点 の形。切り詰めた行だけ Yamamoto 70点 切り詰め の形にする
- 続けて 3人 平均81.7点 の形で、平均を小数第1位まで表示する
- 最後に全部 free して 確保3回 解放3回 の形で表示する
入出力例
main("Tanaka,85
Yamamotokurosawa,70
Sato,90") → "Tanaka 85点
Yamamoto 70点 切り詰め
Sato 90点
3人 平均81.7点
確保3回 解放3回"
main("Ito,100
Nakamura,60
Kobayashidaisuke,80
Kato,75") → "Ito 100点
Nakamura 60点
Kobayash 80点 切り詰め
Kato 75点
4人 平均78.8点
確保4回 解放4回"
main("Suzuki,64
Mori
Aoki,72") → "Suzuki 64点
Aoki 72点
2人 平均68.0点
確保2回 解放2回"