第一回 GNU C の書き方 (1)

正しいプログラムを書け

まずは GNU 流の C の書き方について説明する。詳しくは、 GNU Coding Standards を見て欲しいが、ここではそこではあまり明確になっていない事柄についても言及する。

当然基本はとにかく正しいプログラムを書くことである。 しかしここでは C の解説をする余裕はないので、適当な参考書や教科書を参照して欲しい。 また、GNU では ANSI C さえサポートすれば良いことになっていることに気を付けて欲しい。 もし non-ANSI をサポートしたければ、 Automake を使えば、ansi2knrによって自動的に変換することもできる。 Automake についてはいずれ説明したい。

基本は2タブ

なぜかはよく分からないが、GNU ではインデントは2タブが基本である。 2タブというのはあまり正確な言い方ではない。 タブが2つあるわけではなく、インデントの幅が2文字分という意味である。 多分 Lisper が多いことが関係あるのだと思うが、定かではない。

まずは例を挙げる。

   1 #include <stdio.h>
   2 
   3 int
   4 main (void)
   5 {
   6   printf ("Hello, World!\n");
   7   return 0;
   8 }

これは伝統的な「世界よ、こんにちは」プログラムである。 ANSI C なので、当然 mainint を返す。 また、引数は省略せず、void なら void と書かねばならない。 ここでは printf を呼んでいるので、stdio.hinclude している。 また最後はちゃんと 0 を返さねばならない。

さて、注目するのはそれぞれの括弧の配置と printf の位置である。 関数の定義に使う大括弧は開く方も閉じる方も一番左に寄せる。 その理由は GNU Coding Standards に書かれているのでここでは述べない。 関数の引数の括弧は一個スペースを使って、関数名と括弧の間を空ける。 これは定義だけでなく、呼び出しでも同様である。 少なくとも GNU はこの方が読み易いと信じている。 この辺は好みの問題だが、とにかく GNU ではこうなっているのである。 また、関数の中では各文が2つのスペースでインデントが付けられていることに注意して欲しい。

すでに辟易してきた人もいるかもしれないが、実際には Emacs を使えば、別に苦労しない。 設定をイジってなければ、デフォルトで GNU インデントになっているからである。 vi で GNU インデントを守るのは一苦労なので、やめた方が賢明だ。

if 文

C には様々な制御構造があるが、それらをどうインデントするかも決まっている。

まず if 文である。

   1 FILE *fp;
   2 
   3 fp = fopen ("hoge", "r");
   4 if (! fp)
   5   {
   6     perror ("fopen");
   7     return 1;
   8   }

hoge というファイルを開いて、それが失敗したときの処理の例である。 これを、

   1 if ((fp = fopen ("hoge", "r")) != NULL)

とは書かないことも重要なのだが、あまり気にせず、if 文の方を見てみよう。 括弧の始まりはやはり2文字下げている。 さらにその中も括弧からまた2文字下げる。 閉じ括弧は開き括弧と同じ位置にインデントしなければならない。

もちろん、括弧の付かない if 文はこうなる。

   1 if (! fp)
   2   die ("fopen");

さて、少し脱線すると、GNU では NULLNULL とはあまり書かないようにすることが認められている。 ANSI では 0NULL に適切に変換されることが保証されている。 だから、0 をわざわざ NULL と書くのは冗長だという考え方だ。 それに NULL と書くと、システム間で NULL の定義が異なったりして、意外と欝陶しいのである。 だから、

   1 if (fp == NULL)

よりも

   1 if (! fp)

の方が好ましい。 それにこの方がすっきりしているし、可読性も悪くなっていないことに注意して欲しい。 もちろん、NULL と書いた方が分かりやすいときは NULL を使う方が良い。

他にも else があるときには気を付けないといけないのは、別に GNU に限ったことではない。 曖昧さを取り除くために、ちゃんと括弧で括ろう。

   1 if (x)
   2   if (y)
   3     ...
   4   else
   5     ...

は駄目で、

   1 if (x)
   2   {
   3     if (y)
   4       ...
   5     else
   6       ...
   7   }

としよう。

while 文

while はほとんど if と同じである。

   1 char buf[BUFFERLEN];
   2 
   3 while (fgets (buf, BUFFERLEN, fp))
   4   {
   5     if (strcmp (buf, "hoge") == 0)
   6       break;
   7 
   8     printf ("%s", buf);
   9   }

これは FILE * である fp から一行ずつ読んで、もしそれが hoge という文字列ならやめて、そうでなければ画面に出力する。

do while 文

do ... while はこう書く。

   1 do
   2   {
   3     printf ("%s", buf);
   4   }
   5 while (fgets (buf, BUFFERLEN, fp));

要するに、大括弧と同じ行には他のものは書くな、同一の制御要素は同じ列に並べろ、ということである。

switch 文

switch 文はこんな感じ。

   1 switch (type)
   2   {
   3   case TYPE_HOGE:
   4     ...
   5     break;
   6   case TYPE_BOO:
   7     ...
   8     break;
   9   default:
  10     ...
  11   }

default にも break を入れるかどうかは好みの分かれるところだが、個人的には入れた方が break を必ず付ける習慣が身に付くので良いと思っている。 GNU では特には規定されていないようだ。

ポインタ

非常に細かいことだが、GNU ではポインタな変数を宣言するとき、アスタリスクと名前の間を空けないことになっているようだ。

   1 char *p;

と書き、

   1 char * p;

とはしない。 また脱線すると、Linux のソースの良くないのはこういうのがきちんと統一されていないことだ。 他にも細かいことを挙げると、書いた人によってまちまちで非常に気持ちが悪い。 どのようなインデントを使うにしても、一つのソフトウェアの中では常に同じ書式を使うように心掛けて欲しい。

コメントを書け!

出来るだけコメントを書くように気を付けよう。 これは誰もが分かっていることで、なおかつ、なかなか守られていないことの一つだ。 書いたときは分かりきっていても、しばらく経てば、自分のソースでさえ忘れてしまう。 そういうとき、コメントは非常に役に立ち、場合によってはコメントがなくて意味が分からないという理由だけで、一から書き直さないといけないこともある。 それはすごく無駄だし、GNU のように、管理者が換わることもあるような、長期に渡るプロジェクトでは、自分以外の人間にもちゃんと理解できるようにしたい。

まず関数の定義では必ずその上に説明を付けなくてはならない。 そこには返り値や引数の意味だけでなく、どのようなときにどのような振る舞いをするのか、あるいは、何を仮定して作られているかを簡潔にまとめておく。 例えば、これだけでは不十分だ。

   1 /* 0 if successful, 1 if fails.  */
   2 int
   3 hoge (void)
   4 ...

これでは中をちゃんと見ないと何をやっているか分からないし、どういうタイミングで呼ぶべきかも理解できない。 だから、

   1 /* Do hoge for ... Return 0 if HOGE is not initialized yet, otherwise
   2    return 1.  */

のように書く。 なお、変数の値を参照するときには (要するにオブジェクト指向で言うところのインスタンス)、その変数名を全て大文字で書く。 変数名には常に小文字を使うので、これでしっかり区別が付けられる。

もちろん、変数の宣言やマクロの定義でもしっかりコメントを書こう。 例えば、

   1 int
   2 hoge (void)
   3 {
   4   int tmp;
   5 
   6   tmp = ...;
   7   boo (&tmp);
   8 }

このぐらいなら tmp が何なのかすぐには分かるだろうから、特に付けなくても良いが、これがもっと長くなると、段々分かりにくくなる。 だから、

   1 int
   2 hoge (void)
   3 {
   4   /* A temporary storage for getting a duplicated file descriptor.  */
   5   int tmp;
   6 
   7   tmp = ...;
   8   boo (&tmp);
   9 }

のように書くだけで、ぐんとソースが読みやすくなる。

当然のことだが、コメントには英語を使おう。 将来、あなたのネイティヴな言語は理解できない人が読まなければならないかもしれない。 それに処理系によっては上手く ASCII 以外の文字を処理できないかもしれない、ということを忘れてはならない。 どうせコメントに必要な英語などたかが知れているので、このぐらいの英語は不自由なく使えるようにちゃんと努力しなきゃ。

まとめ

今回はインデントの付け方を中心に話した。 次回は、変数や関数の名前の付け方、プリプロセッサの使い方、また、もっと大きなレベルでの構成方法について書く予定である。 質問、意見、批判などは okuji at enbug dot org まで送って欲しい。


次の回 / はっきんぐ・うぃず・ぐにゅー に戻る。


Copyright © 1999,2000,2007 Yoshinori K. Okuji

Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.

HackingWithGnu/GnuStylePartOne (last edited 2007-03-13 17:39:29 by YoshinoriOkuji)