34章 ファイルシステム

http://www.geocities.jp/ky_webid/c/034.html

さて、ようやくファイルのお話が出てきました。

ここを覚えれば自分で何かファイルを開いて解析するようなプログラムを作れるようになれそうな気がします。

fopenというのが出てきました。ファイルのオープンを行う関数です。

オープンモードというファイルをどのように開くかのオプションがあるのですが、これがかなり多い。

モード 意味
r テキストファイルを読み込み用に開く
w テキストファイルを書き込み用に開く
a テキストファイルを追記用に開く
rb バイナリファイルを読み込み用に開く
wb バイナリファイルを書き込み用に開く
ab バイナリファイルを追記用に開く
r+ テキストファイルを読み書き両用に開く
w+ テキストファイルを読み書き両用に開く
a+ テキストファイルを読み書き両用で追加あるいは作成する
rb+ / r+b バイナリファイルを読み書き両用に開く
wb+ / w+b バイナリファイルを読み書き両用に開く
ab+ / a+b バイナリファイルを読み書き両用で追加あるいは作成する

いっぱいありますねー。

とりあえずこれだけ覚えるのは厳しいので読み込みように開く「r」と書き込み用に開く「w」だけ覚えておいて後は用途ごとに都度調べるようにしようと思います。

問題1

キーボードからファイル名を入力してもらい、そのファイルを開くプログラムを書いて下さい。正しくないファイル名が入力されたときエラーメッセージを出して下さい。

ファイルを開くだけのプログラムですね。

int main () {
    char str[81];
    FILE *fp;
    
    fgets(str,81,stdin);
    
    fp = fopen(str,"r");
    if ( fp == NULL ) {
        printf("%s open error",str);
        return 1;
    }
    
    fclose(fp);
    return 0;
}

うまくいきました!

・・・・・・・・と、思いきや。

fgetsでファイル名を入力すると改行コードまで引っ付いてきます。

で改行コード付きでfopenしようとしてもそんなファイルがないと怒れられてしまいまいた。

ということで改行コードを削除しないといけません。

やってみます。

int main () {
    char str[81];
    FILE *fp;
    size_t len;
    
    fgets(str,81,stdin);
    
    len = strlen(str);
    if ( *(str+len-1) == '\n' ) {
        *(str+len-1) = '\0';
    }
    
    fp = fopen(str,"r");
    if ( fp == NULL ) {
        printf("%s open error",str);
        return 1;
    }
    
    fclose(fp);
    return 0;
}

strlenで文字列の長さを取得し、strのポインタを長さ-1まで移動させると文字列の終端に着きます。そこの値が改行コードだったら\0で上書きしてしまうという処理です。

一応コレで完成です。

ただ、「正しくないファイル名が入力されたときエラーメッセージを出して下さい」というのがちょっとわからなかったです。どう判定すればいいのか。

答え合わせ

概ねあってました。

正しくないファイル名のくだりは、要はfopenの戻り値を必ずチェックしろって意味だったんですかね。ともあれ正解です。

問題2

ファイル名を渡すと、そのファイルを追記用のテキストファイルとして開く自作関数を作って下さい。ファイルポインタを戻り値として返すようにして、エラーチェックも行えるようにして下さい。

追記用のファイルオープンは「a」ですね。やってみます。

FILE *addopen (char *filename) {
    return fopen(filename,"a");
}

int main () {
    char str[81];
    FILE *fp;
    size_t len;
    
    fgets(str,81,stdin);
    len = strlen(str);
    if ( *(str+len-1) == '\n' ) {
        *(str+len-1) = '\0';
    }
    
    fp = addopen(str);
    if ( fp == NULL ) {
        printf("%s open error",str);
        return 1;
    }
    
    fclose(fp);
    return 0;
}

忠実に実装してみました。

自作関数を経由してっていうところ以外は問題1とほぼ同じ答えになってます。

答え合わせ
FILE *append_open(char *filename) {
    static FILE *fp;  /* 呼び出し側からアクセスするためstaticが必要 */
    
    fp = fopen( filename, "a" );  /* 追記モード */
    return fp;
}

戻り値としてローカル変数を返すには、その変数はstaticである必要があります。でなければ、関数が終了した時点で、変数はメモリ上から消えてしまいます。

あれ?ローカル変数のポインタであればともかく、ポインタ変数の場合は大丈夫だった気がするのですが・・・。ポインタ変数は確かにこの関数が終了する時点で消えるけど、このポインタが参照しているデータは消えないから戻り値としても返してOKだという認識だったのですが違うんだろうか。

実際に試してみました。

FILE *addopen (char *filename) {
    FILE *fp;
    fp = fopen(filename,"r");
    return fp;
}

int main () {
    char str[81];
    FILE *fp;
    size_t len;
    
    fp = addopen("main.c");
    if ( fp == NULL ) {
        printf("%s open error",str);
        return 1;
    }
    
    fgets(str,81,fp);
    printf("%s",str);
    
    fclose(fp);
    return 0;
}

これを実行したところ、ちゃんとファイルの中身が表示されました。static使わなくてもポインタの場合はいけると思うんですが・・・。

んーコンパイラの違いとかで何か変わるのかもしれません。ちょっと頭が混乱してきました・・・。

とりあえずここはかなりクリティカルな部分だと思うので誰か知っている方がいたら教えて欲しいところです。

問題3

NULLとは具体的にはどんな値であるか調べるプログラムを作って下さい。

調べる・・・。調べる?

んんん、とりあえずやれることをやってみますか。

int main () {
    printf("%d\n",NULL);
    printf("%s\n",NULL);
    printf("%p\n",NULL);
    return 0;
}
$ main
0
(null)
00000000

とりあえず色々なフォーマットで出力してみました。

NULLとはなんなんでしょうか。いまいちわかりません。

答え合わせ

実行すると「0」が表示されます。NULL の値は、ほとんどのコンパイラでは 0 です。ただし、NULL はポインタ以外の意味で使うべきできはありません。

このプログラム VisualC++ では警告が出ます。また、NULL は、あくまでもアドレスを保持している訳で、そもそも「%d」を使って出力することが正しい行為だとも言えません。printf関数で、アドレスを表示するには「%p」というフォーマット指定が使えます。

わかったようなわからないような。とにかくNULLは0という数値を指しているアドレスということになるんでしょうか?

ここはまた調べる必要がありそうです。





今回の章は色々と疑問を残す形になってしまいました。今後これらの疑問が影響しなければ良いのですが・・・。