[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
この節では、GNUソフトウェアを書くときの一番良いC言語の使い 方について助言を与える。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
C関数の本体を開始する開き大括弧をゼロ列目に置き、他の開き大括弧や開き丸 括弧や開き角括弧をゼロ列目に置かないようにするのは重要だ。いくつかのツー ルは、C関数の始まりを探すのに、ゼロ列目の開き大括弧を捜す。これらのツー ルはそういう風にフォーマットされていないコードでは上手く動かないだろう。
関数定義で、関数の名前がゼロ列目で始まっていることも重要だ。人々はこれの おかげで関数定義を探すのが楽になり、あるツールがそれらを認識するのも楽に なるかもしれない。こうして、適切な書式は次のようになる。
static char * concat (s1, s2) /* Name starts in column zero here */ char *s1, *s2; { /* Open brace in column zero here */ ... } |
あるいは、もしANSI Cを使いたいなら、次のように定義をフォーマットす る。
static char * concat (char *s1, char *s2) { ... } |
ANSI Cでは、もし引数が上手く一行に収まらないなら、次のようにそれを 分ける。
int lots_of_args (int an_integer, long a_long, short a_short, double a_double, float a_float) ... |
関数の本体では、次のようにフォーマットされたコードを好んでいる。
if (x < foo (y, z)) haha = bar[4] + 5; else { while (z) { haha += foo (z, z); z--; } return ++x + bar (); } |
我々は開き丸括弧の前とコンマの後にスペースがあるとプログラムを読むのがよ り簡単であることを見出している。とりわけコンマの後は。
式を複数行に分けるとき、演算子の後ではなく、それの前で分ける。こうするの が正しいやり方だ。
if (foo_this_is_long && bar > win (x, y, z) && remaining_condition) |
字下げが同じところで、異なる優先度の二つの演算子を持たないようにしなさい。 例えば、こう書いてはいけない。
mode = (inmode[j] == VOIDmode || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j]) ? outmode[j] : inmode[j]); |
代わりに、字下げが入れ子を表すよう、余分な丸括弧を使う。
mode = ((inmode[j] == VOIDmode || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j]))) ? outmode[j] : inmode[j]); |
Emacsがそのコードを適切に字下げするよう、余分な丸括弧を入れなさい。例え ば、次の字下げは手でやるといい感じだが、Emacsは台なしにしてしまう。
v = rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000 + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000; |
でも開き括弧一組を加えると問題は解決する。
v = (rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000 + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000); |
do-while文は次のようにフォーマットする。
do { a = foo (a); } while (a > 0); |
フォームフィード文字 (control-L) を使って、プログラムを(関数の中ではなく) 論理的な位置でページに分割してほしい。ページがどれぐらいの長さかなんて問 題じゃない。印刷されるページに合わせなくていいのだから。フォームフィード は行の中にそれ自身だけを置くべきだ。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
どんなプログラムでもそれが何なのか簡単に表すコメントで始まるべきだ。例: `fmt - filter for simple filling of text'.
英語は全ての国のほとんど全てのプログラマが読むことのできる唯一の言語なの で、GNUプログラムでは英語でコメントを書いてほしい。もしあなたが英語を上 手く書けないなら、出来るだけ上手く英語でコメントを書き、他の人々にそれら を書き直すのを手伝ってくれるよう頼んでください。もし英語でコメントを書く ことができないなら、一緒に仕事してくれる誰かを探して、あなたのコメントを 英語に翻訳してもらってください。
それぞれの関数に、その関数が何をやり、どういう引数を受け取り、引数のあり 得る値が何を意味し、そして何に使われるのかを表すコメントを書いてください。 もしCの型が習慣的なやり型で使われるなら、Cの引数宣言の意味をくどくどと複 製する必要はない。もしその利用が(実際には文字列の最初ではなく、二文字目 のアドレスであるchar *
型の引数のような)標準的でないものだったら、 あるいは、(改行を含む文字列は動作保証されない、というような)期待される方 法では働かない値があり得るなら、そう書くのを忘れないようにしなさい。
また、もしあるなら、返り値の意味を説明しなさい。
Emacsのセンテンス・コマンド(sentence command)が働くように、コメントの行 の最後の後に二つのスペースを置いてください。また、完全な文を書き、最初の 単語を大文字で書いてください。もし小文字の識別子が文の最初に来たら、それ を大文字で書いてはいけない! 綴りを変えると違う識別子になる。もし小文字で 文を始めるのが好きじゃないなら、文を違うように書きなさい(例えば、"The identifier lower-case is ...")。
関数の上のコメントは、引数の値について言うときにその引数の名前を使えば、 ずっとはっきりする。変数名それ自体は小文字であるべきだが、変数そのもので はなく、その値について言っているときには大文字で書きなさい。従って、"an inode"よりも、"the inode number NODE_NUM"である。
普通コメントに関数の名前を再び言うことに意味はない。なぜなら、読者は自分で それを見ることができるからだ。関数自身がスクリーンの一番下からはみ出てし まうぐらいコメントが長いときは例外かもしれない。
静的な変数それぞれにも、次のようにコメントがあるべきだ。
/* Nonzero means truncate lines in the display; zero means continue them. */ int truncate_lines; |
すべての`#endif'に、入れ子になっていない(たった数行の)短い条件分岐 の場合を除いて、コメントを付けるべきだ。そのコメントには、 その意味を含めて、終了する条件分岐の状態を記すべきだ。 `#else'はその条件と続くコードの意味を記述するコメントを持つ べきだ。例えば、
#ifdef foo ... #else /* not foo */ ... #endif /* not foo */ #ifdef foo ... #endif /* foo */ |
しかし、対照的に、`#ifndef'では次のようなコメントを書く。
#ifndef foo ... #else /* foo */ ... #endif /* foo */ #ifndef foo ... #endif /* not foo */ |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
関数への全ての引数を明示的に宣言してください。それらが単にint
だか らという理由で省いていけない。
外部関数とソースファイルの後ろに現れる関数の宣言は、ファイルの先頭の近く 一箇所か、ヘッダファイルの中に書くべきだ。関数の中にextern
宣言を 置いてはいけない。
以前、一つの関数内で繰り返し繰り返し異なる値のために(tem
のような 名前で)同じ局所変数を使うのが普通のやり方だった。こうする代わりに、別の 目的毎に別の局所変数を宣言し、意味のある名前を付ける方がより良い。これで プログラムがより理解しやすくなるだけでなく、良いコンパイラの最適化を促進 するのである。また、局所変数の宣言をそれぞれ、それを全て使用する一番小さ い領域に入れることができる。こうすると、プログラムがさらにきれいになるの だ。
大域識別子を隠す局所変数や引数を使ってはならない。
複数行に及ぶ一つの宣言で複数の変数を宣言してはいけない。代わりに、それぞ れ行で新しく宣言を始めなさい。例えば、こうする代わりに、
int foo, bar; |
こう書くか、
int foo, bar; |
あるいは、こうする。
int foo; int bar; |
(もしそれらが大域変数なら、いずれにせよその前にコメントを付けるべきだ。)
他のif
文に入れ子になるif
-else
文があるとき、必ずその if
-else
の周りに大括弧を付ける。従って、次のように決して書 いてはならない。
if (foo) if (bar) win (); else lose (); |
常に次のようにする。
if (foo) { if (bar) win (); else lose (); } |
もしelse
文の中に入れ子になるif
文があれば、次のように、 then
部分をその前のthen
部分のように字下げして一行に else if
を書くか、
if (foo) ... else if (bar) ... |
あるいは、次のように大括弧の中に入れ子のif
を書く。
if (foo) ... else { if (bar) ... } |
同じ宣言で、構造体のタグや変数、typedefを一緒に宣言してはならない。代わ りに、構造体のタグを別に宣言して、それから変数やtypedefを宣言する。
if
条件文内で代入しないようにしなさい。例えば、こう書いてはいけ ない。
if ((foo = (char *) malloc (sizeof *foo)) == 0) fatal ("virtual memory exhausted"); |
代わりに、こう書く。
foo = (char *) malloc (sizeof *foo); if (foo == 0) fatal ("virtual memory exhausted"); |
lint
をおとなしくするのに、プログラムを見苦しくしてはならない。 void
へのキャストを入れないでください。キャストなしのゼロは、可変 引数の関数を呼ぶときを除くと、ヌル・ポインタ定数として全く結構である。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
プログラムの大域的な変数や関数の名前はコメントのように働く。だから、簡潔 な名前を選ばないように。---代わりに、その変数や関数の意味について役に立 つ情報を与える名前を探しなさい。GNUプログラムでは、名前は他のコメント と同様英語であるべきである。
局所変数の名前はもっと短くていい。なぜなら、それらは一つの文脈の中でだけ 使われ、そこでは(たぶん)コメントがそれらの目的を説明している。
ある名前の単語を分けるのに、Emacsの単語コマンドがその中で使えるように、 アンダースコアを使ってください。小文字にしておきなさい。大文字をマクロや enum
定数や一定の取り決めに従う接頭辞のために取っておきなさい。
例えば、ignore_space_change_flag
のような名前を使うべきだ。 iCantReadThis
のような名前を使ってはいけない。
コマンドラインのオプションが指定されたかどうかを示す変数は、オプションの 文字ではなく、オプションの意味にちなんだ名前を付けるべきだ。コメントがオ プションの正確な意味とその文字の両方を記述すべきだ。例えば、
/* Ignore changes in horizontal whitespace (-b). */ int ignore_space_change_flag; |
一定の整数値に名前を定義したいとき、`#define'よりもenum
を使 いなさい。GDBは列挙定数について知っている。
古いSystem Vシステムで不必要な問題を引き起こさないよう、14文字以下のファ イル名を使いなさい。これを試験するのにdoschk
というプログラムを使 うことができる。doschk
はまた、MS-DOSファイルシステムにファイルが 置かれたとしたら、名前が衝突する可能性を試験する。---注意してもしなくて も構わないものだ。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
Unixの世界では、"移植性"は異なるUnixバージョンに移植することを言ってい る。GNUプログラムにとって、この種の移植性は望ましいが、最も重要ではない。
GNUソフトウェアの主要な目的は、GNUカーネルの上で走り、GNU Cコンパイラで コンパイルされ、様々なCPU上で動くことだ。異なるCPU上のGNUシス テム間の多様性の量と種類は、今日のLinuxに基づくGNUシステムやBSDシステム 間の多様性と比較できる程度であろう。だから、絶対に必要な移植性の種類 はかなり限られている。
しかしたくさんのユーザがGNUソフトウェアをGNUでないUnixやUnix-likeシス テムで走らせている。だから、さまざまなUnix-likeシステムをサポートするこ とが望ましい。最も重要ではないけれど。
ほとんどのUnix-likeシステムへの移植性を得る一番簡単な方法はAutoconfを使 うことだ。あなたのプログラムがAutoconfが提供できる以上にホスト・プラット ホームに関する情報を知る必要があることはあまりない。単にそういう情報を必 要とするプログラムの大部分はすでに書かれているから。
準内部的データベース(例えば、ディレクトリ)のフォーマットを使わないように しなさい。もっと高水準の方法(readdir
)があるときは。
MSDOS、Windows、Macintosh、VMS、MVSのような、Unixに似てないシステムにつ いて言うと、それらをサポートするのは普通しない方がいいぐらい大変な仕事だ。
計画されているGNUカーネルはまだ出来てないが、GNU Cライブラリのマニュアル を見ることで、それが提供するであろう機能がどれなのか分かる。GNUカーネル はMachに基づいているから、Machの機能も利用できるだろう。しかしながら、 Machの機能を使用すると、おそらくあなたのプログラムを今日デバッグする困難 に見舞われるだろう(4)。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
int
が32ビットより小さい可能性を扱うために努 力してはいけない。GNUでは16ビットのマシンはサポートしない。
int
オブジェクトのアドレスがまたその一番下のバイトのアドレスである とみなしてはいけない。これはビッグ・エンディアンのマシンでは誤りだ。だか ら、次の間違いをしてはいけない。
int c; ... while ((c = getchar()) != EOF) write(file_descriptor, &c, 1); |
関数を呼ぶとき、さまざまな型のポインタ間やポインタと整数間での違いを心配 する必要はない。ほとんどのマシンでは、いずれにせよ違いはない。違いのある わずかなマシンについて言うと、それらの全てがANSI Cをサポートしてい るので、それらのシステム上でそのコードが動くように、(ANSI Cでだけ使 われるように条件付けされた)プロトタイプを使うことができる。
ある場合には、整数とポインタの引数を無差別に同じ関数へ渡し、いかなるシス テムでもプロトタイプを使わないでも構わない。例えば、多くのGNUプログラム はprintf
やその類いに引数をどんどん渡すエラー報告関数を持っている。
error (s, a1, a2, a3) char *s; int a1, a2, a3; { fprintf (stderr, "error: "); fprintf (stderr, s, a1, a2, a3); } |
実際、これは全てのマシンで動作し、他の"正しい"やり方よりずっと単純だ。 そのような関数に対してプロトタイプを使うことをしないように。
しかしながら、本当に必要としているのでないなら、ポインタを整数にキャスト しないようにしなさい。これらの仮定は実に移植性を減らしており、ほとんどの プログラムでは簡単に避けられる。ポインタから整数にキャストすることが不可 欠な ---アドレスだけでなく型情報をあるワードに収めるLispインタープリタ、の ような--- 場合には、そうして構わないが、異なるワードサイズを扱う明示的な 準備をしなくてはならないだろう。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
Cの実装は十分に違う。ANSI Cは非互換性を減らすが、無くなりはしない。 その一方では、多くのユーザがGNUソフトウェアをANSI以前のコンパイラで コンパイルしたがる。この章では、移植性を不必要に失くさないよう、標準Cラ イブラリ関数をどれぐらいたくさん、あるいは、少なく使うか、推奨する方法を 見せる。
sprintf
の値を使ってはいけない。あるシステムでは書かれた文字の数を 返し、すべてのシステムでそうだというわけではない。
main
はint
型を返すと宣言するべきだ。それはexit
を呼ぶ か、整数状態コードを返すことによって終了するべきだ。決して未定義の値を返 すことができないようにしなさい。
あるシステムでは、システム関数の宣言はほとんどが間違っている。衝突を最小 化するために、システム関数を宣言するのをシステムのヘッダファイルに任せな さい。もしヘッダが関数を宣言しなければ、宣言せずに置いておきなさい。
関数を宣言せずに使うのはきれいじゃないように見えるかもしれないが、実際に はこれが本当に起こるシステム上の、ほとんどのシステムライブラリ関数に対し て上手く働く。対照的に、現実の宣言は頻繁に現実の衝突を引き起こしている。
malloc
やrealloc
を宣言してはいけない。
ほとんどのGNUプログラムは、慣習的にxmalloc
やxrealloc
と名付 けられる関数の中で、たった一回だけそれらを使用する。これらの関数はそれぞ れmalloc
やrealloc
を呼び、結果を確認する。
xmalloc
やxrealloc
はあなたのプログラムで定義されるので、型 の衝突の危険性なしに他のファイルにそれらを宣言できる。
ほとんどのシステム上で、int
はポインタと同じ長さだ。それゆえ、 malloc
やrealloc
の呼び出しは上手く動く。数少ない例外的なシ ステム(ほとんどは64ビット・マシン)では、malloc
やrealloc
の 条件付き宣言を使うか、これらの宣言をそれらのシステムに特化した 設定ファイルに置くことができる。
このことは、あなたが思うよりも問題を起こさない。多くのシステムがまだサポー トしていないので、いずれにせよ、新しいANSI文字列関数は避ける べきだ。使って良い文字列関数は次の通りだ。
strcpy strncpy strcat strncat strlen strcmp strncmp strchr strrchr |
複製や連結の関数は、それらの値を使わない限り、宣言なしで上手く働く。宣言 なしにそれらの値を使うと、ポインタの大きさがint
の大きさと違うシス テムや、おそらく他の場合に失敗する。それらの値を使うのを避けるのはささい なことだから、そうしなさい。
比較の関数やstrlen
は、ほとんどのシステムで、おそらくGNUソフトウェ アが動くすべてのシステムで、宣言なしに上手く働く。少数のシステムで 条件付きでそれらを宣言することが必要だと気付くかもしれない。
検索関数はchar *
を返すと宣言されなければならない。幸運にも、それ らが返すデータ型には多様性がない。しかしそれらの名前には多様性がある。あ るシステムでは、それらの関数にindex
とrindex
という名前を付 けている。他のシステムでは、strchr
とstrrchr
という名前を使 う。あるシステムは両方の名前をサポートするが、どっちも全てのシステムで働 くわけではない。
片方の組の名前を取り出し、プログラム中でそれを使うべきだ。(今日では、新 しいプログラムにはstrchr
とstrrchr
を選ぶのがより良い。それ らは標準のANSI名だから。) それらの名前を両方ともchar *
を返す 関数として宣言しなさい。それらの名前をサポートしないシステムでは、他方の 組のことばをマクロとして定義しなさい。例えば、strchr
と strrchr
の名前を通して使いたいなら、ファイルの始め(あるいはヘッダ に)次のように書いておく。
#ifndef HAVE_STRCHR #define strchr index #endif #ifndef HAVE_STRRCHR #define strrchr rindex #endif char *strchr (); char *strrchr (); |
ここでは、HAVE_STRCHR
とHAVE_STRRCHR
が対応する関数が存在す るシステムでは定義されるマクロだとみなしている。それらを適切に定義する一 つのやり方はAutoconfを使うことだ。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
GNUはあるプログラムのメッセージを様々な言語に翻訳するのを容易にするGNU gettextと呼ばれるライブラリを持っている。あらゆるプログラムでこのライブ ラリを使うべきだ。メッセージがプログラムに現れるとき、それらに英語を使い なさい。そして、それらを他の言語に翻訳するための方法をgettextで提供しな さい。
GNU gettextの使用は翻訳が必要かもしれない、それぞれの文字列の周りに gettext
マクロの呼び出しを付けることを含む ---次のように。
printf (gettext ("Processing file `%s'...")); |
こうすると、GNU gettextが文字列"Processing file `%s'..."
を翻訳さ れたバージョンで置き換えられる。
一度プログラムがgettextを使うことになったら、翻訳が必要な新しい文字列を 加えるとき、gettext
への呼び出しを書く地点を作ってください。
あるパッケージでのGNU gettextの使用は、そのパッケージに対してテキス ト領域名を指定することを含む。テキスト領域名はこのパッケージの翻訳を他 のパッケージの翻訳と分離するのに使われる。通常、テキスト領域名はパッケー ジの名前と同じであるべきだ ---例えば、GNU file utilityのために `fileutils'が使われる。
gettextが上手く働くようにするために、単語や文の構造に仮定を設けるコード を書かないようにしなさい。文の正確なテキストがデータによって変わるのよう にしたいとき、条件付けられた単語や句を単一の文脈構成に押し込むよりも、そ れぞれ完全な文を含む二つ以上の文字列定数を使いなさい。
これがやるべきではないものの例だ。
printf ("%d file%s processed", nfiles, nfiles != 1 ? "s" : ""); |
この例の問題は複数形が`s'を加えることで行われると仮定していることだ。も し書式文字列にgettextを適用するなら、次のように、メッセージが異なる単語 を使うことができるが、
printf (gettext ("%d file%s processed"), nfiles, nfiles != 1 ? "s" : ""); |
複数形が`s'を使うようになお強制されている。これがより良い方法だ。
printf ((nfiles != 1 ? "%d files processed" : "%d file processed"), nfiles); |
このやり方で、二つの文字列それぞれに独立してgettextを適用できる。
printf ((nfiles != 1 ? gettext ("%d files processed") : gettext ("%d file processed")), nfiles); |
こうすると、"file"という単語の複数形を作る、いかなる方法でも実現でき、 "processed"に対して、単語が一致しないといけない言語を扱うこともできる。
同じような問題は次のコードで文脈の構造の水準で現れる。
printf ("# Implicit rule search has%s been done.\n", f->tried_implicit ? "" : " not"); |
このコードにgettext
呼び出しを与えても、すべての言語で正しい結果を 得られるわけではない。なぜなら、いくつかの言語で否定は文中に一つよりもた くさんの場所で単語を加える必要があるからだ。対照的に、gettext
呼び 出しの追加は、もしそのコードが次のように始まるなら、簡単に行える。
printf (f->tried_implicit ? "# Implicit rule search has been done.\n", : "# Implicit rule search has not been done.\n"); |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
mmap
がすべてのファイルに働くとも、すべてのファイルで失敗するとも、 みなしてはいけない。一部のファイルでは上手く行き、他では駄目かもしれない。
mmap
を使う適切な方法は、使いたい特定のファイルで試してみることだ。 ---そして、もしmmap
が働かなかったら、read
やwrite
を 使う他の方法で作業することに頼りなさい。
この用心が必要である理由はGNUカーネル(HURD)はユーザが拡張可能なファイル システムを提供することで、そこではたくさんの異なる種類の"普通のファイル" があり得る。それらの多くはmmap
をサポートするが、いくつかはしない。 プログラムをすべてのそういうファイルを扱えるようにすることは重要だ。
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |