第21回
Cの簡略記法~評価の順序を前提にしたシンプルな書き方

関数呼び出し、代入、比較の複合式

Cでは、ディスク上のファイルを開くときにfopen関数を使います。この関数を例に、関数の呼び出しに絡む式の簡略化を紹介しておきましょう。なお、fopen関数の詳しい機能と使い方はファイル操作の回で説明する予定なので、ここでは簡単に説明しておきます。

ファイルを開く関数

fopen関数の書式は以下のようになります。
<ファイルポインタ> = fopen(<ファイル名>, <モード>);
<ファイル名>で指定したファイルを<モード>で指定したファイルモードで開き、成功すればファイルの情報を保持したFILE型構造体へのポインタ<ファイルポインタ>に返し、失敗すればNULLポインタ(整数の0)を返します。

ファイル名ではパスを含めて指定できます。

ファイルモードはファイルのアクセス方法で、文字列で指定します。主なモード指定は以下の通りです。
"r":読み出しモード。ファイルの中身を読み出す際に指定します。
   <ファイル名>で指定したファイルが存在しなければエラーとなります。
"w":書き込みモード。ファイルに新たに書き込む際に指定します。
   <ファイル名>で指定したファイルが新たに作成されます。
   ファイルが存在していれば、その内容は上書きされます。
"a":追加モード。ファイルの末尾からデータを書き込みます。
   <ファイル名>で指定したファイルが存在すれば末尾から書き込まれ、
   存在しなければ新たに作成されます。
"r+":読み書きモード(読み出し優先)。読み出しと書き込みを行う際に指定します。
   <ファイル名>で指定したファイルが存在しなければエラーとなります。
"w+":読み書きモード(書き込み優先)。読み出しと書き込みを行う際に指定します。
   <ファイル名>で指定したファイルが新たに作成されます。
   ファイルが存在していれば、その内容は上書きされます。
"a+":読み書きモード(追加優先)。読み出しと書き込みを行う際に指定します。
   <ファイル名>で指定したファイルが存在すれば末尾から書き込まれ、
   存在しなければ新たに作成されます。

エラーの有無で処理を切り替える

例えば、"/doc/abc.txt"を読み出しモードで開くなら、以下のようにします。
FILE *fp;    /* ファイル構造体型変数の宣言 */
fp = fopen("/doc/abc.txt", "r");
そして、ファイルが正しく開けたかどうかを、以下のようにして確かめます。
if (fp == NULL) { ---- ファイルが開けなかった場合
  printf("ファイルのオープンエラーです。\n");
}
else {
    : ---- ファイルを開くことができた場合
}
このような場合、以下のような簡略表記ができます。

FILE fp; if ((fp = fopen("/doc/abc.txt", "r")) == NULL) { printf("ファイルのオープンエラーです。\n"); } else { : ---- ファイルを開くことができた場合 }

代入(=)と比較(==)

先に紹介したgetchar関数による処理と同じように、ifの条件式でfopen関数を実行し、その戻り値をfpに代入しています。

関数の実行が代入演算子の=に優先し、さらに代入式全体が()で囲まれて、それとNULLが比較演算子==によって比較されているため、この式は以下のような動作をします。

1.fopen関数を実行する
2.戻り値をfpに代入する
3.ifでfpの値をNULLと等しいか比較する

従って、上記の式は
"/doc/abc.txt"を読み出しモードで開いて、
エラーならメッセージを表示し、
成功すれば処理を行う。
ということになります。

NULLとの比較を省略

式の値が0(NULL)であれば「偽」と見なされるので、以下のようにさらなる簡略化も可能です。
FILE *fp;
if (fp = fopen("/doc/abc.txt", "r")) { ---- NULLとの比較を省略
  printf("ファイルのオープンエラーです。\n");
}
else {
    : ---- ファイルを開くことができた場合の処理
}
しかし、このような書き方はソースを分かりにくくするため、あまりお奨めできません。やはり「fopen関数の戻り値がNULLかどうか?」は、ソースコードに明示しておいた方がいいでしょう。

==と=の間違いを防ぐ

Cのコーディングでは比較演算子の==と代入演算子の=を間違えることもよくあるため、以下のような記述をしてしまう場合があります。
if ((fp = fopen("/doc/abc.txt", "r")) = NULL) {
                                        ↑==を=と入力ミス
現在では、多くの処理系がこのような場合に「左辺値が必要である」といったエラーメッセージを出します。しかし、古い処理系ではエラーとならず、変数fpにNULLが代入されることがありました。すると、仮にfopen関数でファイルを開くことに成功しても、式全体の値は結局「0(代入されたNULL)=偽」になってしまいます。

このようなミスを防ぐために、以下のような書き方をする人もいます。
if (NULL == (fp = fopen("/doc/abc.txt", "r"))) { ...
これだと、以下のように==を=と間違えて記述した場合には、NULLという定数に値を代入することができないため、コンパイル時にエラーとなって間違いに気付きます。
if (NULL = (fp = fopen("/doc/abc.txt", "r"))) { ...
      ↑この式はどのような場合でもコンパイルエラーとなる

単純な比較演算では今でも有効

上のように1行にまとめてしまえば、現在の処理系では記述ミスによるバグを防げます。しかし、以下のような簡略化していない書き方では、"fp = NULL"という代入式が必ず「偽」となってしまうため、ファイルを開けても開けなくてもelseに続く処理が実行されてしまいます。
FILE *fp;
fp = fopen("/doc/abc.txt", "r");
if (fp = NULL) {
  printf("ファイルのオープンエラーです。\n");
}
else {
    : ---- ファイルを開くことができた場合の処理
}
もうお分かりかと思いますが、以下のようにすればこのミスを防げます。変数と定数とを比較する場合に、定数を左辺値とする書き方は意外と役に立ちます。
fp = fopen("/doc/abc.txt", "r");
if (NULL == fp) {
       :