« OECD学力調査で日本の学力低下が明らかに(2) | メイン | 今日,新しく知ったこと »

C How to Program [ レビュー, 教育 ]

月曜アドプロの指定教科書です.
Amazonでも,2ch Booksでも,比較的高評価です.
ただ,誤訳なのか,原著からして間違っているのか分からない誤りがあったり,
そもそも完全に間違っているだろうという間違いもあり,素直にオススメできない.
特に,今回の授業で取り扱った範囲に含まれる問題はかなり重症だった.

問題になったのは11.6~11.9までのランダムアクセスファイルの話.
まず,一番最初に問題になったのは11.8節のリスト11.6だ.


#include <stdio.h>

struct clientData {
int acctNum;
char lastName[15];
char firstName[10];
float balance;
};

main()
{
struct clientData client;
FILE *cfPtr;

if((cfPtr=fopen("credit.dat","r+"))==NULL)
printf("ファイルをオープンできません\n");
else {
printf("顧客の口座番号(1-100)を入力してください(0を入力すると終了)\n? ");
scanf("%d",&client.acctNum);

while(client.acctNum!=0){
printf("名字,名前,取引残高を入力してください\n? ");
scanf("%s%s%f", &client.lastName, &client.firstName, &client.balance);
fseek(cfPtr, (client.acctNum-1)*sizeof(struct clientData),SEEK_SET);
fwrite(&client, sizeof (struct clientData),1,cfPtr);
printf("口座番号を入力してください\n? ");
scanf("%d",&client.acctNum);
}
}
fclose(cfPtr);
return 0;
}

このリストには解さないコードが含まれている.
評価用の小さなコードを下に示す.


#include <stdio.h>
void main(void)
{
    char a[2];
    printf("%p %p %p\n",&a,a,&a[0]);
}

この3つは同じアドレスを示す.
後ろの2つが同じアドレスを示すことは明らかですが,1番目の指し方が分かりません.
先生とTAで話し合った結果,結局分かりませんでした.
これ.何故に同じアドレスを示すのか分かる方,いらっしゃいませんか?
コメント下さい.


そして,本題の議論は11.9節のリスト11.8にある.


#include <stdio.h>

struct clientData {
int acctNum;
char lastName[15];
char firstName[10];
float balance;
};

main()
{
struct clientData client;
FILE *cfPtr;

if((cfPtr=fopen("credit.dat","r"))==NULL)
printf("ファイルをオープンできません\n");
else {
printf("%-12s%-16s%-11s%8s\n","口座番号","苗字","名前","取引残高");

while(!feof(cfPtr)){
fread(&client,sizeof(struct clientData),1,cfPtr);
if(client.acctNum!=0)
printf("%-12d%-16s%-11s%8.2f\n",
client.acctNum,client.lastName,
client.firstName,client.balance);
}
}
fclose(cfPtr);
return 0;
}

このコードが良くない.
20行目のfeofの取り扱いが良くない.
大変残念なことに,Language C FAQに書かれてしまうくらいに良くない.
テキストの通りにテストを行っていくと,この問題には気づかないのだが,
前節のリスト11.6において,口座番号100番にデータを格納してしまうと,
リスト11.8における出力で,100番のデータが2回表示される.

これは真っ当な動作であり,コードが悪い.
feof(cfPtr)はcfPtrがEOFに達しているかどうかを返す.
よって,100番目のデータを読み出した時点で,cfPtrはEOFではなく,
101番目を読みに行って,読めなくて,EOFとなるのだ.
その際,freadは行われないので,clientは上書きされず,100番目のデータがそのまま残っている.
よって,この動作は至極当然の結果なのだ.

これに対する修正コードは先生との議論の結果,以下のように決まった.


if (1==fread(&client, sizeof(struct clientData), 1, cfPtr)
    && client.acctNum != 0)

大変に初歩的なミスで,がっかりを禁じ得ない.


また,これらの一連のコードはさらなる問題を含んでいる.
実習中にハッと気がついて,急いでテストコードで検証したのだが,予想通りの結果だった.
構造体を組んで固定長にして,ランダムアクセスを実現しているのだが,
fopenのオープンモードが"r"だったり,"r+"だったり,"w"だったりと,明らかにテキストモード.
そのため,windows環境においては,0x0Aを出力しようとすると,親切丁寧に0x0D 0x0Aに置き換えてくれます.
わーお!親切!(反語)
よって,ここはバイナリモードにしておかないとまずいのです.

これはamazonのカスタマレビューにも書かれており,全くその通りである.

windows 環境で使う人に注意。
この本では,ランダムアクセスファイルの処理で,ファイルをテキストモードで扱っています。
これは windows ではまずいです。
unix 系では テキストモードもバイナリモードも同じです。しかし,windows では扱いが違うのです。
だから,ランダムアクセスファイルを使うときは,wb, rb+ などのオプションで読まないといけません。
おそらく,著者がUNIX系をもっぱら使う人なのではないか……と思います。

参考になりすぎたので,「参考になりましたか?」に「はい」と投票しておいた.
いや.実際には知っている情報だから,参考にはならなかったけど・・・


というように,どうにもこのテキストには腑に落ちないコードが散在しています.
重箱の隅を突っつく感じですが,typedefを扱ったのに,相変わらずstruct~って書くし,
sizeof演算子は変数ではなく型にかけるし(どちらも正しいが,オレは変数にかける),
for(i=1;i<=100;i++)とか気持ち悪いし,
main()という宣言も個人的には許せない(int main(void)だろう)し・・・


このテキストは2006年5月に初版11刷となっている.
原著の第2版をベースにしているらしく,原著最新版では正しいコードになっているのかも知れない.
原著最新版は第5版のようだ.
最早,別の本のような気もするが・・・
何となく,原著が欲しいなぁって思った今日この頃.


関連:
ヘタレ系DのBlog: 謎の水平グラデバグを解明