プログラム言語 C の推奨されるスタイルとコーディング規範


L.W.Cannon R.A.Elliot L.W.Kirchhoff J.H.Miller J.M.Milner R.W.Mitze E.P.Schan N.O.Whittingson
Bell Labs
Henry Spencer
Zoology Computer System University of Toronto
David Keppel
EECS, UC Barkeley CS&E, University of Washington
Mark Brader
SoftQuad Incorporated Toronto
豊田 英司 (訳)
東京大学大学院数理科学研究科
向 修一 (訳) 浅沼 伸彦 (訳)
東京大学理学部

Abstract

この文書は the Indian Hill C Style and Coding Standards を アップデートしたもので、最後の 3 著者 (Spencer, Keppel, Brader) による 変更をくわえたものである。 C プログラムのコーディング基準が記述されている。 本文書の目的はコーディングスタイルを記述することであり、 機能による組織化 functional organization は範囲外である。


1 はじめに

   この文書は、 AT&Tのインディアンヒル・コミュニティのための C言語についての共通のコーディングに関する規範と勧告を 作成するために、 インディアンヒル・ラボによって組織された委員会の 文書を手直ししたものである。 この文書の対象とするのはCのコーディング・スタイルである。 よいスタイルは、設計を一貫性のあるものにしたり、移植性を高めたり、 誤りを少なくしたりするために役立つであろう。 This work does not cover functional organization, or general プログラムの機能的構成法や、 goto の使用をめぐるような雑多な問題は扱わない。 個々の部分はシステム固有の事情に左右されながらも、 我々1は C のスタイルについて議論した過去の文献 [1,6,8] を、 C を使ったあらゆるプロジェクトにふさわしいような 統一した規範にまとめようとした。 やむをえないことであるが、この規範はすべての状況をカバーするものにはなっていない。 経験と知識に裏打ちされた判断力のほうがより重要である。 普通でない状況に遭遇したプログラマは、 経験豊かなプログラマに相談するか、 経験豊かなプログラマの書いたコード (このルールに従っているものが望ましい) を研究すべきである。

   この文書に書かれている規範は おのずといつでも要求されるようなものではなく、 個々の団体やグループでプログラムの一環として この文書の一部なり全体なりを採用することができる、 というようなものである。 したがって、あなたの団体で他の人々が似たようなスタイルで書くというのは ありそうなことになる。 この規範の目標は究極的には移植性を高め、 メンテナンスの手数を減らし、そして何よりプログラムを分かりやすくする ことである。

   ここで選択されたスタイルには、 恣意的に決めているようなところもいくらかある。 複数のスタイルが混在しているコードは、 悪いスタイルよりも取り扱いにくい。 すでに存在するコードを修正するときは、 この文書の勧告をただ機械的に適用するよりは、 そのコードのスタイル(字下げ、空白の入れ方、コメントの入れ方、 命名法などの慣習)にあわせたほうがよい。

「明快なのがプロフェッショナルである。明快でないのは素人だ」 --- Sir Ernest Gowers.
footnote:

1.
この文書の主張は著者のすべての意見を反映しているわけではない。 これはいまだに進化している文書である。 コメントや提案を pardo@cs.washington.edu または {rutgers,cornell,ucsd,ubc-cs,tektronix}!uw-beaver!june!pardo に送ってほしい。

2 ファイルの構成

   ひとつのソースファイルはいくつかの空行で区切られるべき いくつかのセクションからなる。 ソースファイルの行数の最大値に制限はないが、 1000 行を越えるファイルは扱いにくいものである。 エディタが作業ファイルを作れなくなるかもしれないし、 コンパイルがおそくなるかもしれない。 たとえば何行にもわたる星印2 は、スクロールするのにかかる 時間に比べてもたいした情報を与えないので、勧められない。 79桁より長い行はすべての端末でうまく表示できるわけではないので、 できるならば避けるべきである。 インデントが深くなって長すぎる行ができてしまうのは、 プログラムの構成が悪いせいである。

footnote:

2.
訳注: 星印とはコメントのこと。'*' をコメントの行頭にかくように勧めているため。

2.1 ファイル命名の慣習

   ファイル名はピリオドを含まない base name と、 ピリオドおよびサフィックス3 からなる。 サフィックスはなくてもよい。 base name のはじめの文字はアルファベット、 その後の文字は(ピリオド以外は)数字かアルファベットの小文字に すべきである。 Base name は8文字またはそれ以下、 サフィックスは3文字またはそれ以下4にすべきである。 この規則はプログラムが使ったり作ったりするファイルにも 適用される(例:"rogue.sav")。

   いくつかのコンパイラやツールはファイル名のサフィックスが 決まったものになっていないと動作しない5:

  *
Cのソースファイルは .c でなければならない
  *
アセンブラのソースファイルは .s でなければならない
  *
リロケータブルオブジェクトファイルは .o
  *
インクルードファイルは .h 。 他言語を使う環境では言語名と .h を使うほうがよいであろう (例: "foo.c.h" または "foo.ch")
  *
Yacc のソースファイルは .y
  *
Lex のソースファイルは .l

   C++ はコンパイラに依存したサフィックスをつけないといけない: .c, ..c, .cc, .c.c, .C など。C のプログラムは大部分 C++ のプログラムでもあるので、 この問題には明快な答はない。

   最後に、 make のためには "Makefile" ("makefile" ではなく) を、 ディレクトリやディレクトリツリーの概観を与えるためには "README" というファイル名を使うのが慣例である。

footnote:

3.
訳注: サフィックスは MS-DOS や Windows では拡張子 extension と呼ばれている。

4.
訳注: ピリオドを入れて数える流儀では4文字以下。
この直後の原文の注釈: 8 + 1 + 3 に RCS による ",v" をつけると 14 文字なので Version 7 ファイルシステムに収まる。 MS-DOS では 8 + "." + 3 文字しか許されない。

5.
訳注: これは UNIX に依存した話である。 MS-DOS や Windows では通常アセンブラのソースには .asm を、リロケータブルオブジェクトファイルには .obj を用いる。

2.2 プログラムファイル

   プログラムファイルのなかのセクションの配置として、 次のようなものを提案する:

1.
ファイルのはじめには、このファイルには何が書かれているのかを記述する プロローグをおく。 オブジェクト(関数や外部変数の宣言・定義など)の名前の羅列より、 このファイルの中のオブジェクトの目的の記述のほうが役に立つ。 プロローグには作者、バージョン管理情報、レファレンスなどが 入ってもよい。
2.
すべてのインクルードファイルの読み込みは、 この次にくるべきである。 自明でない理由によっってインクルードファイルを使うときは、 その理由をコメントすべきである。 多くの場合、 stdio.h のようなシステム・インクルードファイルは 他のインクルードファイルよりも先に読み込まなければならない。
3.
ファイル全体に適用されるすべての #define と typedef が次に来る。 通常は「定数」マクロをはじめにおき、 ついで「関数」マクロ、 そして typedef および enum をおく。
4.
次にグローバル(外部)データ宣言がくる。 通常の順番は: extern 宣言、static ではないグローバル変数、static なグローバル変数。 もしひとつのグローバル変数(たとえばフラグ)のとるべき値として、 一群の #define を作るときは、 その #define は該当するグローバル変数のデータ宣言の直後に書くか、 構造体のメンバーの宣言の直後に 1レベル深くインデントして書くべき6である。
5.
関数は最後に来る。 関数はなにか意味のある順番に配置するべきである。 似た関数はまとめて置くべきである。 「普遍的な順に」(抽象化の度合いの近いものをまとめて) 並べるほうが、 「深さの順に」(なるべくその関数を呼び出しているところの近くに) 並べるよりも好まれる。 もし本質的に無関係な関数を大量に定義するときは、 アルファベット順を検討しよう。
footnote:

6.
訳注: 4 節参照。

2.3 ヘッダファイル

   ヘッダファイルとは、Cのプリプロセッサによってコンパイルの前に 他のファイルから読み込まれるファイルのことである。 stdio.h のようないくつかのものはシステムレベルで定義され、 標準入出力ライブラリを使うすべてのプログラムに読み込まれる。 ヘッダファイルは機能別に構成されなければならない、すなわち、 異なったサブシステムの宣言は別のヘッダファイルに入れるべきである。 また、コードをある機種から他の機種に移植するときに変更が予想されるような 宣言は、[機種に依存しないものとは] 別のヘッダファイルにすべきである。

   ライブラリのヘッダファイルと同じ名前のヘッダファイルを作るのは避けること。 もし #include"math.h" がカレントディレクトリで そのファイルを見つけられなければ、 システムの math.h が読み込まれてしまう。 そのようなことが起こるのを期待してそんな記述をするのならばコメントすること。 ヘッダファイルの位置を絶対パスで指定してはならない。 [カレントディレクトリではなく] 指定した場所から読み込ませたいならば <name> 構文またはカレントディレクトリからの相対パス指定を使え。 自分で作ったライブラリのヘッダファイルを読み込ませるのは、 Cコンパイラの「インクルード・パス」オプション (多くのシステムでは -I)を使うのがもっともよい。 こうしておけば、ソースファイルを書き直すことなくディレクトリ構造を 再編成することができる。

   関数や外部変数を宣言するヘッダファイルは、 その関数や変数を定義するファイルでもインクルードすべきである。 こうしておけばコンパイラが常に型チェックができるので、 外部宣言と定義が常に矛盾しないようにできる。

   [どのモジュールからもその変数が見えるようにと] 外部変数の [宣言ではなく] 定義をヘッダファイルで行うのはだいたいいつも悪いアイデアだ7。 たいがいそれはコードのファイル分割がうまくできていない証拠である。また、typedef や初期化付きデータ定義のようなオブジェクトは、 1度のコンパイルに2回現れるとうまくいかない。 いくつかのシステムでは extern をつけていない初期化なしのデータ宣言を 繰り返しても問題になる。 インクルードファイルがネストされていると宣言が繰り返される ことがありえる。これはコンパイルの失敗につながるだろう。

   ヘッダファイルはネストしてはならない。 したがってヘッダファイルのプロローグでは、 このヘッダが機能するために他に #include しなければならない ファイルをあげておくべきである。 いくつかのソースファイルが多数の同じヘッダを インクルードしなければならないというような極端な場合は、 共通する #include をひとつのインクルードファイルにまとめるのも よいだろう。

   ヘッダの二重読み込みが起こらないように各々の .h ファイルを次のように書くのが普通である。

#ifndef EXAMPLE_H
#define EXAMPLE_H
    ... /* example.hの内容 */
#endif /* EXAMPLE_H */
この手の二重読み込み防止機構を、 特にネストしたヘッダを作るために利用してはならない。
footnote:

7.
訳注: この場合定義はどれかひとつのファイルにして、 ヘッダでは extern 宣言だけを行うべきである。

2.4 その他のファイル

   プログラム全体についての問題や「より大きな描像」の文書のために、 "README"というファイルをおくのが普通である。 たとえば、条件コンパイルのフラグとその意味の一覧や、 機種依存個所の一覧を書いておくのがふつうである。

3 コメント

「もしコードとコメントが一致しないならば、きっと両方間違いだろう」 --- Norm Schryer

   コメントは 何が 起こっているのか、 どうやって 実現しているのか、 パラメータは何を意味するのか、 どのグローバル変数を参照し変更するのか、 そしてすべての制限とバグを記述すべきである。 しかしコードから明らかなことは書かないこと。 そんな情報はコードが変わればすぐ意味を失ってしまう。 コードと矛盾するコメントは書かないほうがましである。 短いコメントは 「合計を n で割る」のように どうやって ではなく、 「平均の算出」のように 何を しているのかを記述するものにすること。 C はアセンブラではない。 だから1行ごとに細々と書き込むよりは、 3-10行程度のまとまりごとに何をしているかをコメントしたほうが有益である。

   見苦しいコード [を書かねばならないような場合、これ] を正当化するようなコメントを書かねばならない。 この正当化というのは、見苦しくないように書くとこういう悪いことがある、 というようなものでなければならない。 ただ実行速度が速くなるから、というだけでは 凝った技を使う8 充分な理由にはならない。 そうしない場合よりどれだけ効率がよくなったかを 明示 しなければならない。 コメントは [この修正で生じた] 好ましくない挙動や、 どうしてこれが [その損失と比べても]「よい」修正と 呼べるのかを説明しなければならない。

   データ構造、アルゴリズム等を記述するコメントはブロックコメント に書くべきである。 ブロックコメントは行頭に /* を書いてはじめ、 途中の行はすべて2桁目に * を書き、 最後は 2-3 桁目に */ で終わらせるべきである。 または途中の行を行頭の ** ではじめ、 最後は行頭に */ としてもよい。

/*
 *  これはブロックコメントである。
 *  コメントの本文はタブや空白で同じように字下げすること。
 *  最初と最後の行には他に何も書かないこと。
 */
/*
** こうしてもよい
*/

   grep '^.\*' とやるとソースファイルからブロックコメントだけが 抜き出せることにも注目されたし9。 すでに [別のファイルに] 書いた議論 [を挿入した場合] や著作権表示のように、非常に長いブロックコメントは、 途中の行の2桁目の * を省略してもよい。 関数の中のブロックコメントはふさわしい位置に、 その対象となるコードと同じ字下げをして書き込むこと。 一行だけのコメントはその次の行と同じ字下げで書くこと。

    if (argc > 1) {
        /* コマンド行から入力ファイル名を受け取る */
        if (freopen(argv[1], "r", stdin) == NULL) {
            perror(argv[1]);
        }
    }
非常に短いコメントをコードと同じ行に書いてもよい。 この場合はタブで遠く離して書くこと。 もしコードのブロックに複数のそういったコメントがあるなら、 同じ字下げ位置に揃えること。
    if (a == EXCEPTION) {
        b = TRUE;               /* 特別な場合 */
    } else {
        b = isprime(a);         /* a が奇数の場合のみ動作 */
    }
footnote:

8.
原文: hack

9.
いくつかのプログラム開発支援パッケージでは、 コメント行の内容に応じて他の文字を目印に使っていることがある。 特に関数の前のコメントで `-' を付けた行は関数の目的を 1行で要約しているものと扱われることが多い。

4 宣言

   グローバルな宣言は、1行目からはじめること。 すべての外部データ宣言は extern キーワードを前につけなければならない。 もし外部変数が明示的サイズで定義された配列ならば、 サイズが常に配列から読み取れる (例: '\0' で終わっているリードオンリーな文字列定数)のでない限り、 extern 宣言の際にもそのサイズを明示しなければならない。 サイズをもう一度書くのは、他人が書いたコードから部品を切り取って 使うときに特に役に立つ。

   ポインタを示す `*' は、型の次ではなく 変数名の前に書くこと。

    char*   s, t, u;
ではなく
    char    *s, *t, *u;
前者では `,t' `u' がポインタのように見えるが、そうではない。

   関係のない宣言は、たとえ同じ型でも、別の行に書くべきである。 その名前から意味が明白な定数マクロ以外は、 宣言されたオブジェクトには必ず役割をコメントすること。 変数名、初期化の数値[あれば]、コメントは同じ位置に揃うように タブを入れること。空白よりはタブが望ましい。 構造体や共用体の宣言は、 各要素を各々1行に書き、内容を表すコメントを付けること。 `{' 記号は構造体タグと同じ行に[1つ空白をあけて]、 `}' 記号は独立の行の1桁目に書くこと。

    /*
     * 訳注: type という名前を見て恐怖に駆られる Pascal プログラマへ:
     * C では type は予約語でも何でもない。
     */
    struct boat {
        int     wllength;   /* water line length in metres */
        int     type;       /* 下を見よ */
        long    sailarea;   /* sail area in sqare mm */
    }

/* defines for boat.type */ #define KETCH (1) #define YAWL (2) #define SLOOP (3) #define SQRIG (4) #define MOTOR (5)

これらの #define は、構造体の中の type の宣言の直後におくことがある。 この場合、`#'のあとに充分タブを用いて、 構造体のメンバーの宣言より深く字下げする。

訳注: こういう意味だろう:
    struct boat {
        int     wllength;   /* water line length in metres */
        int     type;
    #               define     KETCH   (1)
    #               define     YAWL    (2)
    #               define     SLOOP   (3)
    #               define     SQRIG   (4)
    #               define     MOTOR   (5)
        long    sailarea;   /* sail area in sqare mm */
    }

しかし具体的な数値が重要ではない場合(そうでなくてもではあるが)、 enum 型を用いるほうがよい。

    enum bt { KETCH=1, YAWL, SLOOP, SQRIG, MOTOR };
    struct boat {
        int     wllength;   /* water line length in metres */
        enum bt type;       /* 船の型 */
        long    sailarea;   /* sail area in sqare mm */
    }

   初期値が重要な意味を持つような場合は かならず 明示的に 初期化すること。 Cのデフォルトの0による初期化を期待して何もしないならば、 最低限そのことはコメントしておくこと。

   初期値が重要な意味を持つような場合は 必ず明示的な初期化をすること。 Cのデフォルトの 0 による初期化を期待して何もしないならば、 最低限そのことはコメントしておくこと。 空の大括弧 ``{}'', による初期化をしてはいけない。 構造体配列の初期化では構造体毎に大括弧でくるむ。 long 型の変数の初期値は、大文字の L で型を明示すること。 小文字では、たとえば ``2l'' (long の「に」)と ``21'', (「にじゅういち」)とは紛らわしい。

int        x = 1;
char        *msg = "message";
struct boat    winner[] = {
    { 40, YAWL, 6000000L },
    { 28, MOTOR, 0L },
    { 0 },
};

   単体で完結していない、 大きなプログラムの構成部品のファイルでは、変数や関数はなるべく static にして、ファイルに固有の、 ローカルなものにする。 特に変数は、余程の理由がない限り外部からは見えないようにする。 逆に他のファイルに含まれる変数を参照するときは、ファイル名も含めて コメントしておく。もしデバッグの際に、見たい static なものを デバッガが隠してしまうようなら、それらを STATIC と宣言して、 適当に #define STATIC とすると良い。

   非常に重要な型は、たとえただの整数型でも typedef してやると、 コードが読み易くなる(もちろんなんでもかんでもそうしたら めちゃくちゃだ!)。構造体を、宣言時に typedef すると 良いこともある。その時は型名は構造体名と同じにする。

typedef struct splodge_t {
    int    sp_count;
    char    *sp_name, *sp_alias;
} splodge_t;

   関数の返り値の型宣言は必ずする。可能ならばプロトタイプ宣言にしよう。 よくある間違いは、数学関係の外部関数を使うとき、 double 宣言を忘れというものだ。コンパイラは int を返すものと思い、無意味な浮動小数点のビット列ができることになる。

``C は、プログラマは常に正しいという思想のもとに設計されている。'' --- Michael DeCorte

5 関数宣言

   関数の前にはブロックのコメント文を短く書き、 何をする関数なのかを、また分りにくい場合にはどう使うかも書くこと。 副作用や、自明でないデザインパターンを使った場合はそのことも書いたほう が良い。 コードから分かるようなことはいらない。

   返り値の型は行頭から 1 タブ10 空けて書き、そのまま改行する。返り値が無い時は void11 を明示する。つまり、デフォルトのint は使わない。返り値の説明は 前述のブロックコメントの中に入れるか、短ければ型宣言の後にタブで区切って 書いても良い。関数名と引数のリストは行頭から書き、それだけで一行を使う。 返り値を渡すための引数は普通先にする (左に書く)。 パラメータの宣言、局所変数の宣言、 コードは全てタブで頭下げする。開き大括弧 ({) には一行を使い、行頭に置く。

   全ての引数は型宣言する。デフォルトの int は使うな。 一般的には関数内で使われる全ての変数の説明を書くべきである。 これはブロックコメント文の中でも良いし、宣言の後に書いても良い (一つの変数に一行を使っているならば)。よくあるループカウンタの ``i'', 文字列ポインタの ``s'', 一文字を入れる int 型の ``c'' などは非明示でも 良いだろう。ある一群の関数の引数や局所変数が大体同じなら、 同じ名前にすれば分かりやすい。逆に、同じ名前で違う事をさせるのは、 関連性のある関数の間ではやめよう。引数の順番も似た様なものにしよう。

   引数や局所変数の説明はタブを使って揃えると良い。局所変数の宣言と 関数のコード本体の間には空行を入れる。

   引数の数が可変長の関数の定義、使用には気を付けなければならない。 というのは C の場合、可搬性の強い保証ができなくなるからである。 固定長の引数を取るインターフェースを作るか、どうしても可変長に したければライブラリマクロを使おう。

   関数の中で、グローバルではない extern な変数や関数を使いたいときは、 逐一関数内で extern 宣言をすること。

   より高レベルでの宣言をオーバーライドするような 局所変数の使い方はやめよう。特にネストされたブロックの中ではいけない。 C では全く合法的なことだけれども、いずれ問題を引き起こすかもしれない。 lint にも -h オプションつきでは怒られるだろう。

footnote:

10.
タブとして 2/4/8 個のスペースを入れるエディタもある。 できれば本当のタブコードを使おう

11.
void 型のないコンパイラでは #define void か #defin void int とすれば良い。

6 空白(ホワイトスペース)

int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\
o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);}

--- 「おぞましいコメント」、不明瞭な C コードコンテスト12 、1984 年
匿名希望

   積極的に空白や空行を入れよう。インデントや空白はコードのブロック構造を 反映せねばならない。例をあげれば、関数のコードの終りと、次の関数のコメントの 間には最低 2 行の空行を入れる、等だ。

   長い条件文は途中で改行する。

if (foo->next==NULL && totalcount<needed && needed<=MAX_ALLOT
    && server_active(current_input)) { ...
これは次の様に書換えるほうが良いだろう。
if (foo->next == NULL
    && totalcount < needed && needed <= MAX_ALLOT
    && server_active(current_input))
{
    ...
同様に、複雑な for 文にも複数行を使った方が良い。
for (curr = *listp, trail = listp;
    curr != NULL;
    trail = &(curr->next), curr = curr->next )
{
    ...
特に ?: 演算子を使う時等、他の複雑な表現でも改行は必須。
c = (a == b)
    ? d + f(a)
    : f(b) - d;
予約語の後に、開括弧で始まる式が続くときは、間に空白を入れねばならない。 ただし、例外は sizeof 演算子で、このときは空白を入れてはいけない。 引数のリストでのコンマの後にも、空白を入れれば、 見易い。 しかしながら、引数のあるマクロ定義では、マクロ名と開括弧の間に 空白を入れてはいけない。C プリプロセッサが引数リストを認識しなくなる からだ。
footnote:

12.
訳注:この大会は実在する。毎年 Usenix で行われている。

7 例

  

/*
 *    空が青いかどうかを、夜ではないことを調べて決定する。
 *    問題点:時々しか正しく判定しない。
 *    FALSE を返すべきときに TRUE を返すことがある。
 *    雲や蝕や日の長さを考慮すべし。
 *    その他:'hightime.c' の 'hour' を使用。
 *    古いバージョンとの互換性を保つため int 型を返す。
 */
    int                        /* true or false */
skyblue()
{
    extern int    hour;        /* 現在時刻 */

    return (hour >= MORNING && hour <= EVENING); }

/*
 *    ポインタ nodep で示されるリンクリストの終要素を見付け、
 *    そのポインタを返す。
 *    存在しなければ NULL を返す。
 */
    node_t *
tail(nodep)
    node_t    *nodep;            /* リストの先頭へのポインタ */
{
    register node_t    *np;        /* NULL に先行 */
    register node_t    *lp;        /* 一つ前を保存 */

    if (nodep == NULL)         return (NULL);     for (np = lp = nodep; np != NULL; lp = np, np = np->next)         ;    /* VOID */     return (lp); }

8 単文(ステートメント)

   一行には一つのステートメントとするべきである。複数書く時は、 強い関連性があるときのみにする。

case FOO:      oogle (zork);  boogle (zork);  break;
case BAR:      oogle (bork);  boogle (zork);  break;
case BAZ:      oogle (gork);  boogle (bork);  break;
for や while ループのボディは空でも一行を使い、 コメントアウトして、空ボディは意図的で、コードが欠けている訳では ないことを示す。
while (*dest++ = *src++)
    ;    /* VOID */

   条件文で、非零であることのテストは省かない。つまり

if (f())
よりも
if (f() != FAIL)
とする方が良い。たとえ FAIL が、 C では false となる 0 であったとしてもである。 このように明示すれば、後で誰かが、「失敗の時は 0 ではなく -1 を返す ことにしよう」と決めたときに、問題が少ない。 更に、比較する値が変化しないときでも、明示するべき例もある。 つまり、 ``if (!(bufsize % sizeof(int)))'' よりも ``if ((bufsize % sizeof(int)) == 0)'' と書く方が良い。 このことで、本質的な条件がブーリアンではなく、数値的なることが 示されるからである。また、絶対に 条件を省いてはいけない場合として、 strcmp (等しいときに 0 を返す)の使用がある。 これは非常によくトラブルの原因になることを 知って欲しい。これに対するおすすめの解決策は、STREQ というマクロの使用である。
#define STREQ(a, b) (strcmp((a), (b)) == 0)

   しかしながら、ノンゼロの明示的な比較は分岐、その他の関数・表現で、 以下の条件を満たすときは、しばしば省かれる。

  *
``0'' と書いて、常に偽とするとき
  *
その名前から返り値の真偽が明らかな関数の呼出し。 isvalid や valid等。 checkvalid の場合は返り値の意味が明瞭でないので、不可。

   ``bool'' という型をグローバルな インクルードファイルで typedef するのはよくある。 特別に名前付けをすれば、可読性が飛躍的に向上する。たとえば

typedef int    bool;
#define FALSE    0
#define TRUE    1
あるいは
typedef enum { NO=0, YES } bool;

などとすれば良い。気を付けなければならないのは、 真として定義したもの (TRUE, YES...) と等しいかどうかを比較しては いけないことである。かわりに、「偽」と等しくないかどうかを 調べなければいけない。一般的には、関数は偽なら 0 を返すことに なっているが、真の時の返り値はノンゼロとしか 定義されていないからである。

if (func() == TRUE) { ...
ではなく
if (func() != FALSE) { ...
とする。可能ならば、前述の例の様に、 関数や変数を、真偽が明瞭なような名前を再定義して、 比較がいらないようにすれば、なお良い。

   代入文の「埋め込み」が行なわれることがある。埋め込みがコンパクトさと 可読性の面から、最良である場合はある。

while ((c = getchar()) != EOF) {
    /* process the character */
}
++ 、 -- 演算子は代入文の働きをするのでしばしば関数呼び出しで副作用を起こす。 ランタイムの性能向上の手段としての埋め込みも、悪い訳ではない。 しかし、作為的な埋め込みは、 引き換えにコードの維持を大変にし易いということはよく覚えておこう。 例えば、
a = b + c;
d = a + r;
を
d = (a = b + c) + r;
とすれば 1 サイクル稼げるが、こうしてはいけない。長い目で見れば、 性能の差はコンパイラの最適化の進化により縮まるが、 手間の差は人間の記憶が薄れるにつれ広がるだけである。

   goto 文は、コードの構造がしっかりしていればそう要らないし、 またあまり使うべきでない。 goto 文が便利なのは主に入れ子になった switch、 for、 while の内部から抜け出す時である。しかし、可能ならば 返り値が成功か失敗で示される関数で内部をばらして、goto を 避けるほうが良い。

    for (...) {
        while (...) {
            ...
            if (disaster)
                goto error;

        }     }     ... error:     /* 後処理 */

goto の行き先のラベルには一行を使い、コードよりもタブを一つ左にする。 また使用に際してそのことを、 どうすると何が起るのかを含めてコメントアウトする (ブロックの前に書くのが良いだろう)。 Continue はループの最初の方に置き、乱用しない。 Break は比較的問題が少ない。

   宣言がプロトタイプではない関数への引数は明示的にキャストせねばならない 時もある。例えば、関数が 32 ビットの long 期待しているところに 16 ビットの int を渡すと、スタックの配置が狂うかも知れない。 似た様な事例はポインタや、整数、浮動小数点型に関して起こる。

9 ブロック文

   ブロック文とは大括弧 { } で囲まれた文である。 大括弧の清書法はいろいろある。 (あれば)自分の環境で一般的な方法にならえば良いし、 好きな方法を選んでも良い(一度決めたものをころころ変えてはいけない)。 他人のコードに手を加えるときは、絶対にそれに従うこと。

control {
        statement;
        statement;
}

これは「K&R13 」流と呼ばれている。これからスタイルを確立する人は、これをお勧めする。K&R では if-else 文の else 、それに do-while 文の while は閉括弧と同じ行に書く。 他の流儀では、閉括弧に一行をとるものが多い。

   ロック文内に幾つか(多過ぎず)ラベルがあるときは、 ラベルは別行立てにする。 switch 文において意図的に break を書かずに、次の case に制御を渡す時は、 その旨コメントしないと、後でコードの維持に泣くことになる。 lint 風に書くのが良いだろう。

switch (expr) {
case ABC:
case DEF:
    statement;
    break;
case UVW:
    statement;
    /*FALLTHROUGH*/
case XYZ:
    statement;
    break;
}

   今の例で最後の break は厳密には不要だが、後々 case が足された時のためにつけておくべきである。 default は、もし使うならば最後に置く。このときは break はいらない。

   If-else 文で、 if と else のどちらかでブロック文を使うなら両方ともブロック文にすること。 これを fully bracketed (あえて訳せば「完全括弧付け」) になっているという。

  

if (expr) {
    statement;
} else {
    statement;
    statement;
}
If-if-else 文が入れ子になっている時も、ブロック化をするべきである。 以下の例では、 (ex1) の後の文をブロック化しないと、意味が変ってしまう。
if (ex1) {
    if (ex2) {
        func_a();
    }
} else {
    func_b();
}

   Else if を使って繰り返し比較するものがある時は、 それを条件文の中のなるべく左に置く。

if (STREQ (reply, "yes")) {
    /* yes に対する処理 */
    ...
} else if (STREQ (reply, "no")) {
    ...
} else if (STREQ (reply, "maybe")) {
    ...
} else {
    /* その他の時の処理 */
    ...
}
こうすれば一種の switch 文の拡張とも考えられる。 また、この様なインデントにより条件と処理が一対一であることが はっきりするので、ネストより優れている。

   Do-while 文ではボディは常に大括弧で括る。

   次のコードはちょっとあぶない。

#ifdef CIRCUIT
#    define CLOSE_CIRCUIT(circno)    { close_circ(circno); }
#else
#    define CLOSE_CIRCUIT(circno)
#endif

    ...     if (expr)         statement;     else         CLOSE_CIRCUIT(x)     ++i;

CIRCUIT が定義されていない場合、 ``++i;'' は expr が偽の時のみしか実行されない! この例の教訓は、マクロ名を大文字とし、 コードを fully-bracketed にすることの意義深さにある。

   If 文の中で break, continue, goto, return を使って、制御を外に移してしまうことがある。 この時は else は書かず、後のコードもインデントしない。 それにより、それ以後はもはや条件判定と無関係なことがはっきりする。

if (level > limit)
    return (OVERFLOW)
normal();
return (level);
footnote:

13.
訳注:Kernighan & Ritchie による巻末文献 [6] のこと

10 演算子

   単項演算子はオペランドとの間に空白を入れてはいけない。 二項演算子は、 `.' と `->' を除いて、一般的には左右のオペランドと空白で分つ。 複雑な式は対応関係を目で追うのが難しいが、それでも 内側ではあまり空白を入れず、外にある演算子のまわりにはスペースを 入れるようにすれば、いくらか読み易い。

   読みにくい式は、複数行にまたぐと良い。改行は、 その付近で最も優先順位の低い演算子の近くにする。 C ではどのような順で式が評価されるかについては曖昧なので、 適当に括弧を使おう(使いすぎると、今度は読みにくくなる。良く考えて書こう)。

   カンマ演算子は for 文等で、複数の初期化や演算をするには有用 だが、普段はなるべく避ける。 また、例えば ?: 演算子のネストの様な、複雑な表現は誤りの元なので、できるだけ避ける。 ただし、マクロによっては(例えば getchar )、 ?: 演算子もカンマ演算子も有用な場合がある。 ?: 演算子では、論理式は括弧に入れ、返り値は二つとも同じ型にする。

11 命名法

   もちろんプロジェクト毎に命名法は異なる。 ここでは一般的なことについて述べる。

  *
アンダースコア (_) が前後についた名前はシステム用に確保してある。 一般のユーザはそれらについて知る必要はないし、 そのような名前を使ってはいけない。 なんらかの識別子が必要なら、 ファイルの属するパッケージ名に対応した一、二文字の接頭辞を付ける。
  *
#define される定数名は全て大文字にする。
  *
Enum で定義する定数は先頭もしくは全ての文字を大文字に。
  *
関数、ユーザ型宣言、変数名、それに構造体、共用体、enum の タグは小文字。
  *
多くのマクロ「関数」は全て大文字である。 getchar や putchar の様に、関数としても存在するマクロの中には小文字のものもあるが、 小文字のマクロは、本当に関数呼出しの様に 機能しなければならない。つまり、引数は 一回だけ しか評価せず、 引数に値を代入してはいけない。 一回しか引数を評価しなくても、関数の様に振舞うマクロを書くのが 不可能な場合もある。
  *
foo と Foo の様に、 大文字・小文字の差しかない二つの名前を使ったりしない。 foobar と foo_bar みたいなのもいけない。 混乱の芽は出ない内に潰そう。
  *
視覚的に間違え易い名前もやめる。 (いかれたフォントでなければ) `l' と `1' と `I' は区別しにくい。 特に `l' という名前の変数はリテラルの `1' みたいなので、避ける。

   グローバルなものには ( enumも含めて)、 どのモジュールに属するのか分るように 共通の接頭辞を付けるのが普通である。 あるいはひとまとめに構造体に入れても良い。 typedef で名前の後に ``_t'' と付けるのはよくある。

   一般的なライブラリと競合するかも知れない様な名前は付けない。 システムによっては予想以上に多くのライブラリを インクルードするし、自分のコードも そのうち拡張することになるかも分らない。

12 定数

   数値定数をそのままコードに書いてはいけない。 その代わり #define を使って、コードが読み易くなるような、意味のある名前を付けてあげよう。 具体的な値を一箇所で定義するメリットは他にもある。 値を変更する必要のある時、一箇所だけ変えれば良いようにすれば、 特に大きなプログラムの場合、管理がずっと楽になる。 また、定まった離散的な値のみをとる変数は enum 型として 宣言すれば、型チェックが余分にできることが多いこともあり、良い。 もし値をコードに直截書くなら、最低限、コメントをして、 数値の由来が分かる様にしておこう。

   定数はちゃんとその用法に合せて定義しよう。 例えば、float 型には 540 として暗にキャストさせるのではなく、初めから 540.0 と書く。 0 や 1 は何かの定義ではなく、それそのものが欲しい場合もある。 例えば

for (i = 0; i < ARYBOUND; i++)
として for ループで配列をさわる。この 0 には全く問題はない。しかし、
door_t *front_door = opens(door[i], 7);
if (front_door == 0)
    error("can't open %s\\n", door[i]);
というのはいけない。なぜなら front_door はポインタなので、比べるならば NULL とでなければいけないからである。 NULL は標準入出力ライブラリのヘッダファイル stdio.h や、最近は stdlib.h 等で定義される。 1 や 0 の様な単純な数値でも、 TRUE と FALSE で言い換えられる様な時は ( YES と NO でも良い。適宜都合良く)、こちらを使おう。

   文字定数は、数値ではなく、char のリテラルで定義する。 非表示文字は、移植性が落ちるのであまりよろしくないが、 (特に文字列の中で)どうしても必要ならば、 エスケープして、3 桁の 8 進数で書く。1 桁ではいけない。 例 '\007'。 とまれ、処理系に依存するコードだということを認識しなければ ならない。

13 マクロ

   マクロでは、定義式中に現われる引数をそれだけで括弧に入れないと、 複雑な式がマクロの引数として渡された時に、演算子の優先順位の関係で おかしな動作をすることがある。 引数関係の副作用の解決法は、副作用の無い様な式を引数にすること くらいである(何にせよ、悪いことではない)。 また、できればマクロで引数を評価するのは一回だけにしよう。 関数と厳密に動作が等しいマクロを書くのは不可能な場合もある。

   getc や fgetc など関数でもあるマクロもある。 その様な場合、マクロの変更が自動的に関数に反映されるよう、 マクロは関数の実装として使うべきだろう。 マクロを関数に、或はその逆に置き換える時は注意が必要である。 引数は関数では値渡しだが、マクロでは置換である。 マクロを安心して使うには、定義に細心の注意が必要なのだ。

   グローバルなもの(変数でも関数でもその他もろもろ)は 局所的にはオーバーライドされている可能性があるので、 マクロからの呼び出しは避ける。 引数の値が、直截代入したり、あるいは代入演算子の左辺に置かれる等して 変更される可能性のある時は、コメントすること。 ポインタの参照先に代入するのは構わない。 参照変数以外の引数は取らないマクロ、長いマクロ、関数の エイリアスのマクロには、定義時に空の引数リストを付ける。例:

#define    OFF_A()    (a_global+OFFSET)
#define    BORK()    (zork())
#define    SP3()    if (b) { int x; av = f (&x); bv += x; }

   マクロはを使えば関数呼出しと復帰にかかるオーバーヘッドを 節約できるが、マクロが長くなると相対的な節約量は小さい。 その時は関数にしよう。

   マクロがセミコロンで終ることをコンパイラが約束していないと 困る場合もある。

#define    SP3()    if (b) { int x; av = f (&x); bv += x; }

if (x==3) SP3(); else BORK();

もし、 SP3 呼出しの後のセミコロンを省くと、 else は知らぬ間に SP3 の定義にある if を受けることになる。 しかし、セミコロンを付けると else はどの if ともマッチしない! SP3 の安全ではある書き方は、
#define SP3() \\
    do { if (b) { int x; av = f (&x); bv += x; }} while (0)
というのがある。ただ、 do-while を手で書くのは面倒で、しかもコンパイラやツールによっては ``while'' の条件が定数なことで噛みついてくる。 ステートメント宣言のためのマクロは役に立つだろう。
#ifdef lint
    static int ZERO;
#else
#    define ZERO 0
#endif
#define STMT( stuff )        do { stuff } while (ZERO)
としておいて、 SP3 を
#define SP3() \\
    STMT( if (b) { int x; av = f (&x); bv += x; } )
と宣言するのだ。この STMT の様なマクロで、細かいタイプミスが知らぬ間にプログラムに 影響を与えるのを防げる。

   マクロで予約語を使う時は、全体が括弧に入っていないといけない。 これは キャストや、 sizeof、 それからさっきの例等は除く。

14 条件付コンパイル

   条件付コンパイルは機種依存性がからむ時やデバッグ、それに コンパイル時にオプションを渡す時などに有用である。 しかし、 様々な制限が互いに重なり、予期せぬ動作をすることは少なくないので、注意しよう。 機種依存性を #ifdef する時は、 機種が特定されていなければデフォルトマシンとせずに、 エラーになる様にする。 (インデントして ``#error'' としておいて、古いコンパイラでも問題が起きないようにしておくこと)。 また最適化を #ifdef するときは、 デフォルトはコンパイル不可能なプログラムではなく、 最適化ナシにしておこう。最適化をしていないコードで必ず テストしておくこと。

   #ifdef 部分のテキストは、たとえ false でもプリプロセッサが スキャンするかも知れない。たとえ #ifdef COMMENT みたいなコンパイルされない部分でも、 どんなテキストにしても良い訳ではない。

   #ifdef はソースファイルではなく、ヘッダファイルに書いておく。 プログラム全体で使われる様なマクロの定義には #ifdef を使おう。 例えば、メモリ割り当てをチェックするヘッダファイルをこんな風に するのはありそうだ ( REALLOC と FREEの定義は省く)。

#ifdef DEBUG
    extern void *mm_malloc();
#    define MALLOC(size) (mm_malloc(size))
#else
    extern void *malloc();
#    define MALLOC(size) (malloc(size))
#endif

   条件付コンパイルは一般に機能面でのチェックを行うべきで、 機種や OS 依存は避けた方が良い。

#ifdef BSD4
    long t = time ((long *)NULL);
#endif
上のコードは二つの点で良くない。 より良いコード法のある 4BSD があるかも知れないこと、 それと今のが最善である 4BSD ではないシステムがあるかも知れないこと、 である。 そうではなく、 TIME_LONG や TIME_STRUCT などの define タグ を使って、適当なものを config.h などで定義すれば良い。

15 デバッグ

``C Code. C code run. Run, code, run... PLEASE!!!'' --- Barbara Tongue

   enum では、最初の定数はノンゼロにするか、 あるいはエラーを示すものにする。

enum { STATE_ERR, STATE_START, STATE_NORMAL, STATE_END } state_t;
enum { VAL_NEW=1, VAL_NORMAL, VAL_DYING, VAL_DEAD } value_t;
こうすると、 初期化していない変数はしばしば自発的に「エラー」になってくれる。

   返り値がエラーでないか、たとえ失敗しない筈の関数であっても、 チェックをせよ。 close() や fclose() は失敗が定義されていて、実際失敗することがあることは 知っておこう。それまでの全てのファイル操作が成功していても、である。 関数ではエラーはチェックして、返り値に反映するか、 きれいにプログラムを終了するようにしておこう。 デバッグ用やエラーチェック用のコードは十分書いておき、 完成してもそのままにしておくのが良い。 「不可能な」エラーも調べること。[8]

   assert を使って関数が各々正しく定義された値を渡されていること、 そして途中の結果におかしいところがないことを確認せよ。

   なるべく少なく #ifdef を使って、デバッグコードを埋め込もう。 以下の例では ``mm_malloc'' をデバッグ時のメモリ割り当ての関数としておけば、 MALLOC と書けば適切な関数が呼び出される。 しかも #ifdef の氾濫でコードが見苦しくなるのが避けられ、 またメモリ割当ての様子も、 デバッグ後とデバッグ中のみの余分なメモリの確保の差がはっきりする。

#ifdef DEBUG
#    define MALLOC(size)  (mm_malloc(size))
#else
#    define MALLOC(size)  (malloc(size))
#endif

   配列等境界は、オーバーフローのしようがない場合様なでもチェックする。 データの書き込み先のサイズが可変の時は、それを関数に maxsize という名前の引数として渡す。 書き込み先のサイズが不明かも知れないならば、 「境界のチェック無し」を意味する定数を定義しておく。 範囲からはずれている場合には、関数はエラーを示す値を返すか、 プログラムを停止するようにしておこう。

/*
 * 引数:'src' はコピー元のナルで終わる文字列、
 *       'dest' はコピー先。'maxsize' は 'dest' のサイズで、
 *       不明な時は UINT_MAX をとる。
 *       'src' も 'dest' も UINT_MAX よりも短くなければならず、
 *       'src' は 'dest' 以下の長さを持つ
 * 返り値:成功すれば 'dest' のアドレス、失敗は NULL
 *         失敗でも 'dest' は変更される。
 */
    char *
copy (dest, maxsize, src)
    char *dest, *src;
    unsigned maxsize;
{
    char *dp = dest;

    while (maxsize-- > 0)         if ((*dp++ = *src++) == '\\0')             return (dest);

    return (NULL); }

   結局は、倍速だが間違った答えを返すプログラムは、無限に遅いのと同じだし、 ぶっとんだり正しいデータを壊したりするプログラムもまあ似た様なものだ。 それが真実だ。

16 移植性

``Cはアセンブラのパワーと、アセンブラの 移植性を兼ね備えている。''
--- 作者不明、Bill Thackerの発言の改作

   移植性の高いコードのメリットは言うまでもない。 この章では、移植性の高いコードを書くためのいくつかのガイドラインを与える。 ここでは、「移植性」とは、同じソースファイルを 異なったマシン上で、ヘッダファイルの挿入やコンパイラに与えるフラグの変更 だけで、コンパイルし実行できることを意味する。 ヘッダファイルにはマシンによって異なる#defineやtypedefが 含まれることになる。 一般的に、新しい「マシン」とは、異なるハードウェアや、 異なるオペレーティングシステム、異なるコンパイラ、 またはそれらの組み合わせを意味する。 文献[1]にはスタイルと移植性の両方について有用な情報が含まれている。 以下に、移植性の高いコードをデザインするときに考慮すべき落とし穴や、 やっておいた方がよいことを列挙する。

  *
移植できるコードを先に書き、細かい最適化について悩むのは 必要になったマシンでだけにする。 最適化されたコードは分かりにくいことが多い。 あるマシンでの最適化が別のマシンにとっては悪いコードをもたらしうる。 性能を上げるための技巧[原文:hack]にはコメントし、できるだけローカライズせよ。 コメントでは、それがどのようなしくみでなぜ必要だったか (例、「ループが6億万回[原文:6 zillion times]実行される」) を説明する。
  *
ある種のものは本質的に移植不可能であることを認識せよ。 例えばProgram Status Wordのような特定のハードウェアレジスタを 扱うコードや、アセンブラやI/Oドライバのような特定のハードウェアを 扱うコードである。 そのようなものでも、ルーチンやデータ構成の多くを機種に依存しないように することができる。
  *
機種に依存しないコードと機種に依存するコードが別個のファイルに入るように ソースファイルを構成せよ。 そうすれば、そのプログラムを新しいマシンに移植するときに、 何を変更すればよいのか判断するのがずっと容易になる。 そして、適切なファイルの先頭部分に、機種依存性をコメントせよ。
  *
「実装に依存する」とされる動作はすべて、機種(またはコンパイラ)依存性 として扱うべきである。 そのようなコンパイラやハードウェアの動作は、完全にひねくれたやり方だ と考えよ。
  *
ワードの大きさに注意せよ。 オブジェクトが直感的な大きさと違うことがある。 ポインタはintと同じ大きさとは限らない。 ポインタ同士が同じ大きさとも限らないし、 自由に交換可能だとも限らない。 次の表は、様々なマシンとコンパイラのCにおける、基本的な型のビット数を 示している。
型pdp11VAX/1168000Cray-2UnisysHarris80386
シリーズファミリ1100H800
char8888988
short16168/1664(32)18248/16
int163216/3264(32)362416/32
long32323264364832
char*16323264722416/32/48
int*16323264(24)722416/32/48
int(*)()163232645762416/32/48
マシンによってはある型に対して2つ以上のサイズが可能なことがある。 使われるサイズは、コンパイラやコンパイル時のフラグに依存することがある。 次の表に大多数のシステムにおける「安全な」型のサイズを示す。 符号なしの数は符号ありの数と同じビット数である。
型最低少なくとも
ビット数〜以上の大きさ
char8
short16char
int16short
long32int
float24
double38float
any *14
char *15any *
void *15any *
  *
void* 型はどんなオブジェクトへのポインタも収容できる大きさがあることが 保証されている。 void(*)() 型はどんな関数へのポインタも収容できることが保証されている。 一般的なポインタが必要な場合は、これらの型を使うこと。 (古いコンパイラでは、それぞれ char* と char(*)(), を使う。) ポインタは使う前に必ず正しい型にキャストしなおすこと。
  *
たとえ int* と char* の大きさが同じでも、それらのフォーマットは異なるかも知れない。 例えば、次のコードは sizeof(int*) が sizeof(char*) と同じ大きさを持つマシンのいくつかでは失敗する。 これは free が char* を期待しているのに対し、 int* を渡されているからである。
int *p = (int *) malloc (sizeof(int));
free (p);
  *
オブジェクトのサイズがその精度を保証しないことに注意すること。 Cray-2は int を収容するのに64ビットを使うが、 long を int にキャストしてまた long に戻したものは、32ビットに丸められていることがある。
  *
整数の 定数 ゼロは、どのポインタ型にもキャストしてよい。 その結果得られるポインタはその型の ヌルポインタ と呼ばれ、同じ型の他のどんなポインタとも違う値を持つ。 ヌルポインタは常に定数ゼロと等しい。 ヌルポインタは値ゼロを持つ変数と等しくないことがある。 ヌルポインタは必ずしも全てのビットがゼロの状態で格納されているとは限らない。 2つの異なる型に対するヌルポインタは異なることがある。 ある型のポインタを別の型にキャストすると、その別の型のヌルポインタになる。
  *
ANSIコンパイラでは、同じ型の2つのポインタが同じ記憶領域を アクセスしている場合、それらを比較すると等しい。 ゼロでない整数定数をポインタ型にキャストすると、他のポインタと等しく なることがある。 ANSIでないコンパイラでは、同じ記憶領域をアクセスするポインタでも 比較すると異なるという結果がでることがある。 例えば、次の2つのポインタは、比較すると等しいことも等しくないこともある。 また、同じ記憶領域をアクセスしているかも知れないし、していないかも知れない 14。
((int *) 2 )
((int *) 3 )
もしもヌルでない「魔法の[原文:magic]」ポインタが必要なら、 何か記憶領域を割り当てるか、ポインタを機種依存として扱うこと。
extern int x_int_dummy;        /* in x.c */
#define X_FAIL    (NULL)
#define X_BUSY    (&x_int_dummy)
#define X_FAIL    (NULL)
#define X_BUSY    MD_PTR1        /* MD_PTR1 from "machdep.h" */
  *
浮動小数点数には精度と範囲があり、 それらはオブジェクトの大きさとは関係ない。 したがって、32ビット浮動小数点数は、違うマシン上では違う値で 桁溢れ(桁落ち)することがある。 また、 4.9 × 5.1 は、違うマシンでは違う答えを出すだろう。 丸めや切り捨ての違いによって、驚くほど異なる答えが出てくる。
  *
マシンによっては、 double の範囲や精度がが float よりも小さいことがある。
  *
マシンによっては、 double の最初の半分が、同じ数の float になっていることがある。 これには頼らないこと。
  *
符号付きのcharに注意。 例えばVAXでは、 charは式の中では符号付きになる。 これは他の多くのマシンとは異なっている。 符号付き/符号なしを仮定したコードは移植性がない。 例えば、 array[c] は、もしも c が正でなければいけないのに実際は符号付きで負だと、動作しない。 もし符号付き、符号なしのcharを仮定しなければならないなら、 SIGNED ないし UNSIGNED とコメントせよ。 符号なしの動作は、 unsigned char を使うことで保証される。
  *
ASCIIコードを想定するのは避けよ。 もし想定しなければならないならば、コメントしてローカライズせよ。 charは8ビットより(ずっと)多くなることがあることを忘れずに。
  *
多くのマシンでの2の補数表記を利用したコードは使うべきでない。 算術演算を等価のシフト演算で置き換える最適化は、特に疑わしい。 もし絶対に必要なのなら、機種依存のコードに#ifdefを使うか、 演算を#ifdefを使ったマクロで行うべきである。 節約できる時間と、コードが移植された時に不可解で厄介なバグができる 可能性を天秤にかけよ。
  *
一般的に言って、もしもワードのサイズや範囲が重要なら、 「大きさの決まった」型をtypedefせよ。 大きなプログラムなら、中心となるヘッダファイルで、 共有できるような幅の決まった型をtypedefすれば、 そのような型の変更やそれを使ったコードの発見が容易になる。 unsigned int 以外の符号なしの型は、 大いにコンパイラ依存である。 もし16ビットか32ビットで十分な大きさの単純なループカウンタを使うときは、 int を使えば、そのマシンにとって最も効率のよい(自然な)単位を得られる。
  *
データの配置もまた重要である。 例えば、 マシンによって4バイト整数はどんなアドレスでも始められたり、 偶数のアドレスからのみ始まったり、4の倍数のアドレスからのみ 始められたりする。 したがって、ある種の構造体では、 たとえ与えられた要素が全てのマシンで同じサイズだったとしても、 異なるマシン上では異なるオフセットに要素が置かれることになる。 実際、32ビットのポインタと8ビットのcharは、3種類のマシン上で 3種類のサイズを持つことがある。 当然の結果として、オブジェクトへのポインタは自由に交換できない。 奇数アドレスから始まる4バイトへポインタを通して整数を書き込むと、 成功することもあるかも知れないし、 コアを吐くかも知れないし、 (途中で他のデータを上書きしながら)静かに失敗するかも知れない。 ポインタとcharの間は、アドレスが1バイトごとでないマシンで とりわけトラブルスポットとなる。 配置の問題とローダーの特性を考えると、 続けて宣言された変数がメモリ上で隣り合っているとか、 ある型の変数が他の型で適切に使えるように配置されているなどと 考えるのは非常に軽率だということになる。
  *
VAXのようなマシンではワードの中のバイトは アドレスが増えるほど位が高くなる(little-endian)のに対し、 他の、例えば68000などではアドレスが増えると位が下がる(little-endian)。 ワードの中のバイト順が、より大きいオブジェクト(例えば、double word) の中のワード順と同じとは限らない。 それゆえに、オブジェクトの中のビットの左右順に依存するコードは全て、 特別な注意を要する。 構造体のメンバー中のビットフィールドは、2つの分かれたフィールドが結合して 1つのユニットとして扱われないかぎり、移植可能である。[1,3] 実際のところ、2つの変数を結合するのはすべて、移植不可能だ。
  *
構造体には使われない穴があるかも知れない。 型をごまかすためのunionを疑え。 特に、変数をある型として格納して別の型で取り出すのは良くない。 unionには明示的なタグフィールドが有用かも知れない。
  *
コンパイラによって構造体を返すのに異なった慣習が使われる。 このことは、ライブラリが、違うコンパイラでコンパイルされたコードに 構造体の値を返すときに問題となる。 構造体のポインタでは問題はない。
  *
引数を渡す仕組みに関して仮定をしないこと。 特にポインタのサイズや引数の評価順、サイズなど。 例えば次のコードは、非常に移植性が悪い。
    c = foo (getchar(), getchar());

    char foo (c1, c2, c3)     char c1, c2, c3; {     char bar = *(&c1 + 1);     return (bar);            /* c2を返さない事が多い */ }

この例には多くの問題がある。 スタックが上がったり下がったりするかも知れない (それどころか、スタックなど存在しないかも知れない)。 引数は渡されるときに広げられるかも知れない、 例えば char は int, として渡されるかも知れない。 引数は左から右へ、右から左へ、あるいは任意の順番でプッシュされうる。 レジスタに渡される(プッシュ自体されない)こともありうる。 評価の順番もプッシュされる順番と異なるかもしれない。 コンパイラによっては、いろいろな(非互換な)呼び出しの慣習を持ちうる。
  *
マシンによっては、ヌルの文字ポインタ ((char *)0) がヌル文字列へのポインタと同じように扱われていることもある。 これに頼らないこと。
  *
文字定数を書き替えてはいけない15。 1つの特に悪名高い(悪い)例を示す。
s = "/dev/tty??";
strcpy (&s[8], ttychars);
  *
アドレス空間には穴があることがある。 配列の中の割り当てられていない要素のアドレスを (行列を実際に格納する[原文:store]前でも後でも) 単純に計算するのは、 プログラムをクラッシュさせるかも知れない。 もしアドレスが比較に使われていれば、 プログラムは走ることもあるかも知れないが、データを上書きしたり、 間違った答えを出したり、無限ループするかも知れない。 ANSI Cでは、オブジェクトの配列の中へのポインタが その配列の終端の後の最初の要素を指すことが合法である。 これはふつう、古い実装では安全である。 この「外側」にあるポインタは、逆参照してはならない。
  *
与えられた型の全てのポインタに対して定義されている比較は、 == と != だけである。 <、 <=、 >、 それに >= は、2つのポインタが同じ配列(または同じ配列の後の最初の要素)を 指しているときのみ移植可能である。 同様に、ポインタに算術演算子を使うのも、両方のポインタが同じ配列の中か その後の最初の要素を指している時のみ移植可能である。
  *
ワードのサイズはまた、シフトとマスクにも影響を与える。 次のコードは68000の一部では、intの右端3ビットのみをクリアする。 他のマシンでは、その上の2バイトもクリアする。
x &= 0177770
代わりに、
x &= ~07
を使えばどのマシンでもちゃんと動く。 ビットフィールドにはこのような問題はない。
  *
Cでは評価の順番がほとんどの所で明示的に定義されていないので、 式の中の副作用は、 コンパイラによって意味が異なるコードを作る原因となりうる。 悪名高い例に次のものがある。
a[i] = b[i++];
上の例では、 b の添え字がインクリメントされていないことしか分からない。 a の添え字の i の値は、 インクリメントの前のものかもしれないし、後のものかも知れない。
struct bar_t { struct bar_t *next; } bar;
bar->next = bar = tmp;
2番目の例では、 ``bar->next'' のアドレスは ``bar'' に値が代入される前に計算されるかも知れない。
bar = bar->next = tmp;
3番目の例では、 bar が bar->next. の前に代入されることが考えられる。 これは「代入は右から左に行われる」というルールに反するように見える が、合法的な解釈である。 次の例を考えてみよ。
long i;
short a[N];
i = old
i = a[i] = new;
``i'' に代入された値は、代入が右から左へ進んだとしたときの型になって いなければならない。 しかし、 ``i'' に ``(long)(short)new'' という値が代入されるのは、 ``a[i]'' に値が代入されるより先かも知れない。 コンパイラにはいろいろあるのである。
  *
コード中に現れる数値(「マジックナンバー」)には疑いの目を向けよ。
  *
プリプロセッサのトリックは避けよ。 トークンの連結に /**/ を使うようなトリックや、 引数の文字列展開に頼るようなマクロは、確実に壊れる。
#define FOO(string)    (printf("string = %s",(string)))
...
FOO(filename);
が
(printf("filename = %s",(filename)))
になるのは、一部の場合だけである。 ところで、トリッキーなプリプロセッサはいくつかのマシンで偶然に マクロを壊すことがあるので気をつけること。 例えば次の2種類のマクロを考えてみよ。
#define LOOKUP(chr)    (a['c'+(chr)])    /* 意図通りに動く。 */
#define LOOKUP(c)    (a['c'+(c)])        /* 時々壊れる。 */
2番目の LOOKUP は、2種類の違った展開方法があるので、 コードが不可思議な壊れかたをする可能性がある。
  *
現存するライブラリ関数や定義をよく知ること。 (ただし知りすぎてもいけない。 ライブラリの詳しい内部は、外部のインターフェイスと違って、 警告なしに変更されることがある。 また、移植性が低いことが多い。) 文字列比較ルーチンや端末制御ルーチンを自分で書いたり、 システム構成に関する定義を自分で作ったりしようとしてはいけない。 「自前で作る」ことは時間の浪費であり、 またコードを読みにくくする。他人が読むときに、その再実装品が 存在するに足るだけの何か特別なことをしているのかどうかを 判断しなければならないからである。 また、あなたのプログラムが あらゆるマイクロコード・アシストや他のシステムルーチンの性能向上の手段を 利用することを妨げることにもなる。 そしてさらに、これらはバグの宝庫でもある。 もし可能なら、共通したライブラリ間の違いも認識せよ (ANSI、POSIXなど)。
  *
lintを使えるときは使うこと。 これは機種依存性や、その他の矛盾点、プログラムのバグといったコンパイラを通ってしまうものを見つける貴重なツールである。 もしコンパイラに警告を出すスイッチがあるのなら、使うこと。
  *
ブロック外の switch や goto と関連しているブロック内のラベルは、疑うこと。
  *
型が疑わしいときはいつでも、 引数は適切な型にキャストするべきである。 プロトタイプ宣言されていない関数呼び出しの中では、NULLが出てきたら必ずキャストせよ。 型をごまかすための場所として関数呼び出しを使わないこと。 Cにはややこしい拡張ルールがあるので、気をつけること。 例えば、関数が32ビット long を期待しているところへ16ビット int が渡されると、スタックが間違って配置されたり、値が間違った拡張をされたりすることがある。
  *
符号付きの値と符号なしの値の混在した算術演算をするときは、 明示的にキャストすること。
  *
ブロックの外に飛びだすgoto、 longjmp, は、注意して使うこと。 多くの実装では、レジスタの値を元に戻すのを忘れてしまう。 最重要な値はできれば volatile と宣言するか、 VOLATILE とコメントせよ。
  *
リンカの中には名前を小文字に変換するものや、 最初の6文字のみをユニークとして認識するものがある。 このようなシステムではプログラムが静かに壊れることがある。
  *
コンパイラの拡張機能に注意せよ。 もし使うのなら、コメントした上で 機種依存性として扱うこと。
  *
一般的にプログラムはデータセグメントのコードを実行したり、 コードセグメントに書き込んだりはできない。 できるときであっても、安定してできる保証はない。
footnote:

14.
このコードはコンパイルに失敗するか、ポインタを作るのに失敗するか、 ポインタの比較に失敗するか、あるいはポインタの逆参照に失敗する可能性もある。

15.
ライブラリによっては読み出し専用の文字変数を書き替えようとして 元に戻すものがある。 このような壊れたライブラリのせいで移植ができないことがある。 改善されてきてはいる。

17 ANSI C

   最近の新しいCコンパイラは、ANSIで提唱されている標準Cをサポートしている。 可能な限りいつでも、コードを標準Cの枠内で書き、関数プロトタイプや定数格納、volatile格納といった機能を利用すること。 標準Cはオプティマイザによりよい情報を与えることによって、プログラムのパフォーマンスを向上させる。 標準Cは全てのコンピュータが同じ入力言語を受け付けることを保証し、 機種依存性を隠したり機種依存の可能性があるコードに警告を発したりする 機能を提供することで、移植性を向上させる。

17.1 互換性

   古いコンパイラに移植しやすいコードを書くこと。 例えば、 新しい(標準の)キーワード、例えば const や volatile をグローバルな.hファイルで、条件付きで#defineせよ。 標準的なコンパイラはプリプロセッサシンボル __STDC__16 をあらかじめ定義している。 void*型は単純に動作させるのが難しい。 なぜなら古いコンパイラは void は理解しても void* は理解しないからである。 新しい (機種とコンパイラに依存する) VOIDP 型を新たに作るのが一番簡単で、 これは古いコンパイラではふつう char* である。

#if __STDC__
    typedef void *voidp;
#    define COMPILER_SELECTED
#endif
#ifdef A_TARGET
#    define const
#    define volatile
#    define void int
    typedef char *voidp;
#    define COMPILER_SELECTED
#endif
#ifdef ...
    ...
#endif
#ifdef COMPILER_SELECTED
#    undef COMPILER_SELECTED
#else
    { NO TARGET SELECTED! }
#endif

   ANSI Cでは、プリプロセッサ命令のための'#'は 行の中で空白を除く最初の文字でなくてはならないことに留意。 もっと古いコンパイラでは、行の先頭の文字でなくてはならない。

   staticな関数が順方向に宣言を持つ場合、その順方向の宣言は 記憶領域クラスを含む必要がある。 古いコンパイラでは、クラスは ``extern'' でなくてはならない。 ANSIコンパイラでは、クラスは ``static'' でなくてはならないが、 グローバルな変数の場合は ``extern'' と宣言しなくてはならない。 したがって、staticな関数の順方向宣言には、 適切に#ifdefされた FWD_STATIC のような#defineを使うべきである。

   ``#ifdef NAME'' は、 ``#endif NAME'' ではなく、 ``#endif'' または ``#endif /* NAME */'' で終わるべきである。 なお、コードを見れば明白であるので、 このコメントは短い#ifdefには付けるべきでない。

   ANSIの trigraphs は、文字列 ``??'' を含むプログラムを不可解に停止させてしまうことがある。

footnote:

16.
コンパイラの中には __STDC__ を0に定義しておいて、ANSI C規格に部分的に従っていることを 示しているものもある。 残念ながら、どのANSI機能が提供されているかを 知ることはできない。 したがって、そのようなコンパイラは故障品である。 ルール 「無理やりやらされているのでなければ、壊れたコンパイラで物を書くのはやめよ」 を見よ。

17.2 整形

   ANSI Cのスタイルは、2つの注意すべき例外を除いて、標準Cと同じである。 その例外とは、記憶領域修飾子と、引数リストである。

   const と volatile は同じ結合ルールを持っているので、 それぞれの const や volatile は、別々に宣言しなければならない。

int const *s;        /* YES */
int const *s, *t;    /* NO */

   プロトタイプ化された関数は、引数の宣言と定義を 1つのリストにまとめている。 引数について、関数のコメントの中で説明しておくべきである。

/*
 * `bp': 乗ろうとしているボート。
 * `stall': 乗り場のリスト。NULLには決してならない。
 * 乗り場の番号を返す。 0 => 空きがない。
 */
    int
enter_pier (boat_t const *bp, stall_t *stall)
{
    ...

17.3 プロトタイプ

   関数プロトタイプはコードを堅牢にし、速く動かすために 使うべきである。 残念ながら、プロトタイプ化された宣言

extern void bork (char c);
は、定義
    void
bork (c)
    char c;
...
と互換性がない。
このプロトタイプは c がそのマシンに最も自然な型、例えばバイトとして渡されると言っている。 プロトタイプ化されていない(後方互換の)定義は、 c が常に int17 を渡されることを示唆している。 もし関数が格上げ可能な引数を持つのなら、呼び出し側と呼び出される側は同じようにコンパイルされなければならない。 両方ともが関数プロトタイプを使うようにするか、 そうでなければどちらもプロトタイプを使うことはできない。 この問題は、プログラムがデザインされたときに引数が格上げされれば 避けられる。 例えば、 bork は int の引数をとるように定義できる。

   上の宣言は、定義がプロトタイプ化されていれば動作する。

    void
bork (char c)
{
    ...
残念ながら、 プロトタイプ化された文法は、ANSIでないコンパイラが プログラムをはねつける原因となる。

   プロトタイプとも古いコンパイラとも動作する外部宣言を書くのは 簡単である18。

#if __STDC__
#    define PROTO(x) x
#else
#    define PROTO(x) ()
#endif

extern char **ncopies PROTO((char *s, short times));

PROTO は二重かっことともに使わなければならないことに注意。

   結局、 片方のスタイル(すなわち、プロトタイプあり)だけで書くのが最善かも知れない。 プロトタイプなしのバージョンが必要になったら、自動変換ツールを使って 生成できる。

footnote:

17.
このような自動的な型の格上げを widening と呼ぶ。 古いコンパイラでは、wideningのルールは 全ての char と short の引数が int として渡され、 float の引数が double として渡されることを要求している。

18.
PROTO を使うことは「マクロ置換で文法を変えてはいけない」というルールに 違反することに注意。 これよりよい解決法がないのは残念である。

17.4 Pragmas

   Pragma は、管理された方法で機種依存コードを導入するのに使われる。 当然ながら、pragmaは機種依存性として取り扱われるべきである。 残念ながら、ANSIのpragmaの文法では、 マシンに依存したヘッダに隔離することができない。

   pragmaには2つのクラスがある。 最適化 は無視して差し支えない。 may safely be ignored. システムの動作を変更するようなpragma (「必須pragma[原文:required pragmas]」)は、無視できない。 必須pragmaは、pragmaが選択されていない場合にコンパイルが中断するように、 #ifdefを付けておくべきである。

   コンパイラによって、与えられたpragmaの使い方が異なることがある。 例えば、あるコンパイラは ``haggis'' を最適化の合図に使い、 別のものでは、与えられた文に達したときにプログラムを終了させる という指示をするのに使うかも知れない。 したがって、pragmaを使うときには、 常に機種依存の#ifdefで囲まなくてはならない。 pragmaは常に非ANSIコンパイラでは#ifdefを使って除外しなくては ならない。 必ず #pragma, の中の`#'記号はインデントすること。 そうしないと古いプリプロセッサはそこで止まってしまう。

#if defined(__STDC__) && defined(USE_HAGGIS_PRAGMA)
    #pragma (HAGGIS)
#endif

```#pragma'コマンドはANSI規格に書かれていて、 実装で定義されたあらゆる種類の働きをさせる。 GNU Cプリプロセッサでは、`#pragma'は最初にゲーム`rogue'を起動しよう とする。それが失敗すると、ゲーム`hack'を起動しようとする。もしそれが 失敗すると、ハノイの塔を表示するGNU Emacsを起動しようとする。もしそれも 失敗すると、致命的なエラーを出す。 いずれの場合も、プリプロセッサは動作を停止する。''
--- GNU C 1.34のCプリプロセッサのマニュアル。

18 特殊な考慮事項

   このセクションには様々なすべきこととすべきでないこと [原文:do's and don'ts]が書かれている。

  *
マクロ置換で文法を変えてはいけない。 プログラムを侵入者以外には解読困難になってしまう。
  *
離散値が必要なときには、浮動小数点変数を使ってはいけない。 ループカウンタに Using a float を使うのは足をすくわれる大きな原因になる。 浮動小数点数は必ず<=か>=でテストし、 等号比較(==や!=)は決して作らないこと。
  *
コンパイラにはバグがある。 よくあるトラブル箇所には、構造体の割り当てやビットフィールドがある。 一般に、どのバグをコンパイラが持っているかを予測することはできない。 故障していることが分かっている全てのコンパイラの全ての部分を避けることは できるかも知れない。 そうしても有用なものは書くことはできないだろうし、 まだバグに見舞われるかも知れない。 そしてコンパイラがいつの間にか直ってしまうかもしれない。 したがって、コンパイラのバグを「迂回」したコードを書くのは、 特定のバグ付きのコンパイラを使わねばならないときだけにすべきである。
  *
自動整形に頼らないこと。 よいプログラムスタイルから主に利益を得るのは、 プログラマ自身である。 特に手書きのアルゴリズムや疑似コードをデザインする初期段階でそうである。 自動整形は完成された、文法的に正しいプログラムにしか使ってはならない。 だから空白やインデントへの注意がもっとも必要な時には使えない。 関数やファイルの完全な視覚的レイアウトを明確にし、 注意深いプログラマがするくらいの詳細にわたる注意を払うほうが、 プログラマの仕事としては、むしろよい。 (言い換えれば、視覚的レイアウトのいくつかは文法よりも意図によって 表現されるものであり、整形ツールは意図を読めないということである。) 間違いの多いプログラマは、自分のコードを読めるようにするために 整形ソフトに頼るより、注意深いプログラマになるようにすべきである。
  *
論理比較における2番目の ``='' を間違って省略してしまうのは問題になる。 明示的なテストを使うべきである。 暗黙のテストを含む代入は避けること。
abool = bbool;
if (abool) { ...
代入をどうしても組み込んで使うときは、テストを明示的にして 後で「直され」ないようにすること。
while ((abool = bbool) != FALSE) { ...
while (abool = bbool) { ...    /* VALUSED */
while (abool = bbool, abool) { ...
  *
通常の流れから外れたところで変わる変数や、 メンテナンス中に壊れる可能性の高いコードは 明示的にコメントせよ。
  *
最近の新しいコンパイラは、変数を自動的にレジスタに入れる。 register は節約して、最重要だと考える変数を示すのに使うこと。 極端な場合では、2から4つの最重要な値に register を付け、残りに REGISTER. を付ける。 後者の方は、レジスタの多いマシン上では register に#defineすることができる。

19 Lint

   LintはCのソースファイルを検査して、その中の型の不一致や、関数定義と呼び出しの矛盾、プログラムのバグの可能性などを発見し報告するCプログラムチェッカ[2][11]である。 全てのプログラムにlintを使うことが強く勧められる。 そしてほとんどのプロジェクトは、プログラムにlintを公式な受け入れ手続きの一部として使うことを要求することが期待される。

   注意すべきこととして、lintの最善の使い道は、プログラムが公的に受け入れられる前に越えなければならない障壁としてではなく、コードに変更や追加を加えたときに使う道具としてのものである。 lint は問題が起こる前に不明瞭なバグを発見し、移植性を保証する。 lintからのメッセージの多くは実際に何かおかしいという事を示している。 面白い話を1つ。 `fprintf' の引数が1つ足りないプログラムがあった。

fprintf ("Usage: foo -bar <file>\\n");
作者には何の問題も起きなかった。 しかし、一般ユーザがコマンドラインでミスをするたびに、そのプログラムはコアを吐いた。 多くのバージョンのlintはこのバグを捕捉できる。

   多くのオプションは学ぶ価値がある。 いくつかのオプションは合法なものにも文句を付けるかも知れないが、それらは沢山のヘマも見つけだす。 -p19 はライブラリルーチンの一部に対してのみ関数呼び出しの型の一致を調べるので、検出率を最高にするためには、プログラムを-p付きと-pなしの両方でlintすべきであることに留意。

   lintはまたコード中の特殊なコメントも認識する。 これらのコメントはlintがコードに文句を言うのをだまらせたり、 特殊なコードにコメントしたりするのに使われる。

footnote:

19.
フラグの名前は異なることがある。

20 Make

   もう1つの便利なツールがmake[7]である。 開発の際に、 makeは最後にmakeが使われてから変更のあったモジュールだけをコンパイルし直す。 また、他の作業を自動化するのにも使える。 広く使われる表記法に、次のようなものがある:

all常に全てのバイナリをメイクする
clean全ての中間的なファイルを削除する
debugテスト用のバイナリ'a.out'または'debug'をメイクする
depend一時的な依存関係を作る
installバイナリやライブラリなどをインストールする
deinstall``install''の取り消し
mkcatマニュアルページをインストールする
lintlintを走らせる
print/list全てのソースファイルのハードコピーを印刷する
shar全てのファイルのシェルアーカイブを作る
spotlessmake cleanを行い、バージョン管理[原文:revision control]を使ってソースファイルを捨てる
注:Makefileはソースファイルだが削除しない
sourcespotlessが行ったことを取り消す
tagsctagsを走らせる(-tフラグを使うことを勧める)
rdistソースを他のホストに配付する
file.c名前を指定したファイルをバージョン管理で調べる
これに加えて、コマンドラインから (``CFLAGS''のように) Makefileの値を定義したり (``DEBUG''のように) プログラムの値を定義したりできる。

21 プロジェクトにより異なる規範

   個々のプロジェクトではここに書かれた他に規範が追加されることがある。 次に挙げる論点はそれぞれのプロジェクトのプログラム管理グループから 提示されるべきものである。

  *
命名法にどのようなルールを追加するべきか? 特に、グローバルなデータを機能的に分類したり、構造体やunionにメンバ名を付けるための、体系的な接頭辞付けルールを作れば有用であろう。
  *
プロジェクトに特有のデータ改装に対しては、どのようにインクルードファイルをまとめればよいか?
  *
lintの出すメッセージを吟味するのにどのような手順を確立すべきか? lintのオプションに応じた許容レベルを設定し、重要でないメッセージによって本当のバグや矛盾に関するメッセージが隠されてしまうのを防ぐ必要がある。
  *
もしプロジェクトが自前のライブラリ集を立ち上げるのなら、システム管理者にlintライブラリファイル[2]を供給することを計画すべきである。 lintライブラリファイルによって、lintはライブラリ関数が正しい使われ方をしているかチェックすることができる。
  *
どのようなバージョン管理を使う必要があるか?

22 結論

   Cのプログラミングスタイルに一通りの規範が示された。 最も重要なものをいくつか挙げると:

  *
空白とコメントを正しく使い、コードのレイアウトからプログラムの構造を明らかにすること。 簡単に理解できるような、単純な式、文、関数の使用。
  *
将来いつか、自分ないし誰かがコードを修正したり他のマシンで走らせたりするように頼まれる可能性があることを頭に入れておくこと。 不可解なマシンにも移植可能なようにコードを作ること。 最適化は混乱を招き他のマシンでは逆効果[原文:``pessimizations'']になりうるので、ローカライズすること。
  *
スタイルの選択の多くは任意である。 (特にグループの規範に)一貫したスタイルを持つことが、 絶対的なスタイルのルールに従うことよりも重要であること。 スタイルを混ぜるのは、単一のどんな悪いスタイルを使うことより悪い。

   規範は何でもそうだが、有用なものには従うこと。 これらの規範のいずれかに従おうとして問題が起きたなら、 単に無視してはいけない。 地元の大御所か、自分の組織の経験あるプログラマに尋ねてみること。


References


[1]
B.A. Tague, C Language Portability, Sept 22, 1977. This document issued by department 8234 contains three memos by R.C. Haight, A.L. Glasser, and T.L. Lyon dealing with style and portability.
[2]
S.C. Johnson, Lint, a C Program Checker, USENIX UNIX Supplementary Documents, November 1986.
[3]
R.W. Mitze, The 3B/PDP-11 Swabbing Problem, Memorandum for File, 1273-770907.01MF, September 14, 1977.
[4]
R.A. Elliott and D.C. Pfeffer, 3B Processor Common Diagnostic Standards- Version 1, Memorandum for File, 5514-780330.01MF, March 30, 1978.
[5]
R.W. Mitze, An Overview of C Compilation of UNIX User Processes on the 3B, Memorandum for File, 5521-780329.02MF, March 29, 1978.
[6]
B.W. Kernighan and D.M. Ritchie, The C Programming Language, Prentice Hall 1978, Second Ed. 1988, ISBN 0-13-110362-8.
[7]
S.I. Feldman, Make --- A Program for Maintaining Computer Programs, USENIX UNIX Supplementary Documents, November 1986.
[8]
Ian Darwin and Geoff Collyer, Can't Happen or /* NOTREACHED */ or Real Programs Dump Core, USENIX Association Winter Conference, Dallas 1985 Proceedings.
[9]
Brian W. Kernighan and P. J. Plauger The Elements of Programming Style. McGraw-Hill, 1974, Second Ed. 1978, ISBN 0-07-034-207-5.
[10]
J. E. Lapin Portable C and UNIX System Programming, Prentice Hall 1987, ISBN 0-13-686494-5.
[11]
Ian F. Darwin, Checking C Programs with lint, O'Reilly & Associates, 1989. ISBN 0-937175-30-7.
[12]
Andrew R. Koenig, C Traps and Pitfalls, Addison-Wesley, 1989. ISBN 0-201-17928-8.
Cプログラマへの十戒


Henry Spencer


1
頻繁にlintを走らせ、実行結果に目を光らせよ。事実、その観察力と判断力はしばしば汝に勝る。
2
ヌルポインタを追うな。その先には渾沌と狂気が待ち受けている。
3
関数の引数が期待された型になっていなければ、必ずキャストせよ。たとえそれが不要だと確信していてもである。さもなくば、思いもかけぬ時にそれらは汝に残酷な復讐をするであろう。
4
汝のヘッダファイルが汝のライブラリ関数の返り値の型を宣言していないなら、自ら細心の注意を払って宣言せよ。さもなくば、汝のプログラムには惨憺たる災いが降りかかろう。
5
すべての文字列(というよりむしろ、すべての配列)の境界をチェックせよ。汝が``foo''とタイプするところで``supercalifragilisticexpialidocious''とタイプする者が、いつの日か必ずいるからである。
6
関数が問題発生時にエラーコードを返すと知らされているのなら、そのエラーコードをチェックせよ。さよう、たとえそのチェックが汝のコードを 3 倍の大きさにし、タイプする指には痛みをもたらすとしてもである。もし汝が「自分には起こりっこない」と考えれば、神は必ずや汝の不遜に罰を下すであろう。
7
ライブラリを学び、正当な理由なく新しいものを自作しないようにせよ。それは汝のコードを短く読みやすいものとし、日々を快適で生産的にするためである。
8
好むと好まざるとに拘らず、One True Brace Style20 を使って汝のプログラムの目的と構造を汝の仲間に分かりやすくせよ。汝の創造力を問題解決に使うほうが、美しくて理解に邪魔なものを新たに創るよりもよいからである。
9
汝の外部識別子は最初の6文字でユニークになるようにせよ。この厳しい掟は苛立たしく、これが不可避な年月は果てしなく長く思えるであろうが。そうしなければ、汝のプログラムを古いシステムで走らせたくなったその運命の日に、汝は髪を掻き毟り発狂することになるだろう。
10
「この世はみんなVAXだ」などと言い張る下卑た異端信仰は放棄し、拒絶し、絶縁せよ。そしてこの野蛮な崇拝に拘泥する無知な異教徒とは関わりあうな。汝のいまのマシンの寿命は短く、汝のプログラムの寿命は長いこともある。

文語訳聖書風の訳 (豊田英司による)
1
汝屡(しばしば)21 lint を走らしめ、謹みてその託宣を学ぶべし。 誠に、その悟りと裁きしばしば汝にまさればなり。
2
汝ヌルポインタを追ふべからず。その先には混沌と狂気待ち受けをればなり。
3
関数の引数もし期待されし型にあらずば、 たとい不要なりとの確信あるとも、汝すべてキャストすべし。 恐らくは22汝に荒き応報(むくひ)をなさん。
4
汝のヘッダファイル汝のライブラリ関数の返却値の型を宣言しをらずば、 汝みづから謹みてこれを宣言すべし。 恐らくは汝のプログラムに甚だしき災禍(わざわひ)ふりかからん。
5
汝すべての文字列またすべての配列の境を省み糺(ただ)すべし。 汝の ``foo'' と打鍵するところで ``supercalifragilisticexpialidocious'' と打鍵なす者、 いつの日か必ず現れればなり。
6
関数もし禍害(わざわひ)起これる時にエラーコードを返すと説かれあらば、 汝エラーコードを糺すべし。 さよう、たとい汝のコードを三層倍になし、 打鍵なす指に痛みをもたらすといへども。 汝もし「おれには起こりっこない」と考うれば、 神々必ずや汝の不遜に罰を下さん。
7
汝のライブラリを学ぶべし、理由なき再創造を避くべし。 汝のコードを短く読みやすからしめ、日々を楽しく稔り豊かならしむためなり。
8
汝もしこれを憎むとも、One True Brace Style を用ひ汝のプログラムの目的と構造を汝の仲間に判りやすからしむべし。 美しき理解の妨げを新たに創りなすことに汝の力を用ふよりは、 問題を解決することに用ふは善きことなればなり。
9
汝の外部識別子をはじめの六文字にて一意たらしむべし。 この厳しき掟は苛立たしく、 その避け難き日々は汝の前に果てしなく延びるとも。 恐らくは汝のプログアムを古きシステムで走らせんと企みし運命(さだめ)の日に、 汝髪をかきむしり狂はん。
10
「世の中みんな VAX だ」などと説く賤しき異端信仰を、 汝放棄し、拒絶し、絶縁すべし。 またこの夷狄(えびす)めく信仰に頼る蒙昧たる敵と取引をなすべからず。 汝の今の機械の寿命は短く、汝のプログラムの寿命は長かりうればなり。
footnote:

20.
訳注:"K&R Style" とも言う。複数の文をまとめる{を、ifやforなどのキーワードと同じ行の末尾に書く記法。

21.
なるべく文語訳聖書に近い用字用語をこころがけたら ルビなしでは読めないような文章になってしまった。 HTML でルビを書くわけにもいかないのでカッコがきされているのはルビと思われたし。

22.
恐らくは: lest の文語訳聖書での訳語。

HTML generated using htroff at 2 January 1999 23:47:6.