lint が行う検査、lint ライブラリ、および lint フィルタなどに関する lint の参考情報について説明します。
lint 固有の診断は、矛盾した使い方、移植不能のコード、疑わしい言語構造の 3 つの広い条件カテゴリに対して表示されます。この節では、各カテゴリにおける lint の動作の例を示し、どのような対応が可能かを説明します。
ファイル全域とファイル内部における変数、引数、関数の矛盾した使用を検査します。概して lint が古いスタイルの関数に対して検査していたのと同様に、プロトタイプの使用、宣言、引数を検査します。 プログラムが関数プロトタイプを使用していない場合、lint は関数の呼び出しごとにコンパイラより厳しく引数の数と型を検査します。lint は、[fs]printf() と [fs]scanf() の制御文字列の変換指示子と引数の不一致も識別します。
例:
lint はファイル内で呼び出した関数に値を返すことなくそのまま終了してしまうような非 void 型関数にフラグを立てます。以前、プログラマは fun() {} のように戻り型を省略することによって「関数は値を返さない」ということを示しました。しかし、fun() が戻り型 int を持っているとみなすコンパイラには何の意味もありません。この問題を解決するには、戻り型 void の関数として宣言します。
lint はファイル全域で非 void 型関数が値を返さず、しかも式の中でその値が使用されている場合や、これとは反対に、関数が返す値があとの呼び出しで時々または常に無視されるという場合を検出します。値が常に無視されるのは、関数定義が不十分だと考えられます。時々無視されるのは、間違ったプログラミングスタイルをとっていることが考えられます (エラー状態のテストが行われていないなど)。strcat()、strcpy()、および sprintf() のような文字列関数や、printf() と putchar() のような出力関数の戻り値を検査する必要がない場合、その問題となる呼び出しは void 型にキャストしてください。
lint は次の場合に変数や関数を識別します。宣言されたが定義または使用されていない。使用されたが定義されていない。定義されたが使用されていない。したがって、一緒に読み込まれるファイルのすべてにではなくその一部に lint が適用されると、lint は次の場合に警告を出します。
そのファイルで宣言された関数と変数がほかの場所で定義または使用された。
そのファイルで使用された関数と変数がほかの場所で定義されていた。
そのファイルで定義された関数と変数がほかの場所で使用された。
1 つめの場合を抑制するには -x オプションを、あとの 2 つの場合を抑制するには -u オプションを使用してください。
lint は、デフォルトでいくつかの移植不能コードを知らせます。lint が -p または -Xc を指定して呼び出されると、さらに多くのケースが診断されます。lint は ISO C 規格に一致しない言語構造を検査します。-p および -Xc のもとで発行されるメッセージに関しては、「4.6.2 lint ライブラリ」を参照してください。
例:
いくつかの C 言語処理系では、signed や unsigned と明示的に宣言されない文字変数は、一般に -128 から 127 の範囲の符号付きデータとして扱われます。別の処理系では、これらは一般に 0 から 255 の範囲の負でないデータとして扱われます。
char c; c = getchar(); if (c == EOF) ... |
そこで EOF が値 -1 を持つテストは、文字変数が負でない値を取るマシンでは常に失敗します。-p オプションで呼び出した lint は、普通の char が負の値を取る可能性があるような比較をすべて検査します。しかし前述の例では、c を signed char で宣言しても、問題が除去されるのではなく診断が除去されるだけです。これは、getchar() が入力可能な文字と明確な EOF 値を返さなければならず、char がその値を格納することができないためです。これは、処理系ごとに定義される符号拡張から生ずるもっとも一般的な例です。これにより、lint の移植性オプションを注意深く使用すると移植性に関係しないバグを発見するのに役立つということがわかります。ここでは c を int で宣言します。
同様の問題がビットフィールドにもあります。定数値がビットフィールドに代入される場合、その値を保持するにはフィールドが小さすぎる場合があります。int 型のビットフィールドを符号なしデータとして扱うマシンでは、int x:3 に対し許容される値は 0 から 7 の範囲で、符号付きデータとして扱うマシンでは、-4 から 3 の範囲です。int 型を宣言した 3 ビットフィールドは後者のマシンでは値 4 を持つことはできません。-p を指定して呼び出された lint は、unsigned int または signed int のみが移植可能なビットフィールド型です。コンパイラは、ビットフィールドの型 int、char、short、および long をサポートしますが、これらは unsigned、signed またはそのどちらでもない場合があります。さらにコンパイラは enum のビットフィールドの型もサポートします。
大きなサイズの型が小さなサイズの型に代入されると、バグが発生することがあります。有効なビットが切り捨てられると正確な値を保持できなくなります。
short s; long l; s = l; |
lint は、デフォルトでこのような代入すべてを知らせます。診断は、-a オプションを指定して呼び出すことにより抑制することができます。どのオプションを指定して lint を呼び出しても、ほかの診断をも抑制する可能性があることに注意してください。2 つ以上の診断を抑制するオプションについては、「4.6.2 lint ライブラリ」にあるリストを参照してください。
あるオブジェクト型へのポインタをより厳密な境界整列要求を持つオブジェクト型のポインタにキャストすると、移植性がなくなることがあります。lint のフラグは次のようになります。
int *fun(y) char *y; { return(int *)y; } |
大部分のマシンでは、int は char とは異なり任意のバイト境界から開始することができないため、lint はフラグを立てます。-h を指定して lint を実行することによってこの診断を抑制することができます。この場合もまた、ほかのメッセージを抑制する可能性があります。汎用ポインタ void * を使用すればほかの影響を回避することができます。
ISO C は、複雑な式の評価順序を定義していません。この意味は、関数呼び出し、入れ子になった代入文、またはインクリメントとデクリメント演算子から副作用が生じる場合 (すなわち、式評価の副作用として変数が変更される時)、副作用の生じる順序はマシンへの依存度が高いということです。デフォルトでは、lint は副作用で変更されたり同一式内でほかの場所に使用される変数を知らせます。
int a[10]; main() { int i = 1; a[i++] = i; } |
この例での a[1] の値は、あるコンパイラでは 1、別のコンパイラでは 2 という可能性もあります。 ビット単位の論理演算子 & が論理演算子 && の代わりに誤って使用されると、この診断を引き起こします。
if ((c = getchar()) != EOF & c != ’0’) |
lint は、プログラマの意図には反するが、言語構造上は正しい箇所についても報告します。次に例を示します。
unsigned 変数は常に負ではない値を持ちます。そのため次のテストは
unsigned x; if (x < 0) ... |
常に失敗します。一方、
unsigned x; if (x > 0) ... |
これは次のように指定するのと同じことです。
if (x != 0) ... |
最初の例は意図したものではない可能性があります。lint は、負の定数または 0 と unsigned 変数との疑わしい比較を知らせます。unsigned 変数を負数のビットパターンと比較するには、その負数を unsigned にキャストします。
if (u == (unsigned) -1) ... |
または、接尾辞 U を使用します。
if (u == -1U) ... |
lint は、副作用が予想される状況で使用される副作用のない式、すなわちプログラマの意図に反した式を知らせます。代入演算子が予想されるところ、つまり副作用が予想されたところで等価演算子が存在する場合は追加の警告が発行されます。
int fun() { int a, b, x, y; (a = x) && (b == y); } |
lint は、論理演算子とビット単位の演算子 (具体的には、&、|、^、<<、>>) の両方が混在する式に括弧を入れるように注意を与えます。これは演算子の優先度を間違って解釈することにより、不正確な結果になる可能性があります。ビット単位の演算子 & の優先度は論理演算子 == より低いため、式はユーザーの意図とは異なる次のような式として評価されます。
if (x & a == 0) ... |
この式は、次のように評価されます。
if (x & (a == 0)) ... |
-h を指定して lint を呼び出すと、この診断は抑制されます。
lint ライブラリを使用して、呼び出したライブラリ関数とユーザープログラムとの互換性を検査することができます。関数戻り型の宣言、関数が期待する引数の数と型などを検査します。標準 lint ライブラリは、C 言語処理系で供給されるライブラリに対応し、一般にはシステムの標準位置であるディレクトリに格納されています。慣例では、lint ライブラリは llib-lx.ln という形の名前を持ちます。
lint 標準 C ライブラリの llib-lc.ln は、デフォルトで lint コマンド行に追加されます。ライブラリ関数との互換性の検査は、-n オプションを指定して呼び出すことにより抑制することができます。そのほかの lint ライブラリは、-l に対して引数として指定することでアクセスされます。次に例を示します。
% lint -lx <ファイル 1>.c <ファイル 2>.c |
この例では、lint ライブラリ llib-lx.ln との互換性について、<ファイル 1>.c と <ファイル 2>.c の関数と変数の使用方法を調べるよう lint に指示します。定義だけからなるライブラリファイルは、厳密に通常のソースファイルと .ln ファイルとして処理されます。ただしライブラリファイルで関数と変数が矛盾したまま使用されるか、またはライブラリファイルで定義されてもソースファイルでは使用されない関数と変数に対しては警告を出しません。
自分の lint ライブラリを作成するには、C ソースファイルの先頭に NOTE(LINTLIBRARY) 指令を挿入し、次いで -o オプションとそのライブラリ名を与える -l オプションとともにそのファイルに対して lint を実行してください。
% lint -ox <ファイル 1>.c <ファイル 2>.c |
前述のコマンド行により、NOTE(LINTLIBRARY) が先頭に付いたソースファイル中の定義だけがファイル llib-lx.ln に書き込まれます。lint -o と cc -o の類似に注意してください。ライブラリは、同様に関数プロトタイプ宣言のファイルから作成されます。 ただし、NOTE(LINTLIBRARY) と NOTE(PROTOLIB(n)) の両方が宣言ファイルの先頭に挿入されている場合は別です。n が 1 の場合、プロトタイプ宣言は古いスタイルの定義と同様にライブラリ .ln ファイルに書き込まれます。n がデフォルトの 0 の場合、処理はキャンセルされます。-y を指定して lint を呼び出しても、lint ライブラリを作成することができます。次のようなコマンド行があるとします。
% lint -yx <ファイル 1>.c <ファイル 2>.c |
前述のコマンド行で指定された各ソースファイルは NOTE(LINTLIBRARY) で開始したかのように扱われ、 その定義だけが llib-lx.ln に書き込まれます。
デフォルトでは、lint は標準位置で lint ライブラリを検索します。標準位置以外のディレクトリで lint ライブラリを検索するように lint に指示するには、-L オプションを使用してディレクトリのパスを指定します。
% lint -L<ディレクトリ> -lx <ファイル 1>.c <ファイル 2>.c |
拡張モードでは、lint は基本モードで生成される .ln ファイルより多くの情報が格納された .ln ファイルを生成します。拡張モードの lint は、基本モードまたは拡張モードのどちらの lint で生成された .ln ファイルでもすべて読み取って理解することができます。基本モードの lint は、基本モードの lint を用いて生成された .ln ファイルだけを読み取って理解することができます。
デフォルトでは、lint は /usr/lib ディレクトリのライブラリを使用します。これらのライブラリは基本 lint 形式です。makefile を一度実行して新しい形式の拡張 lint ライブラリを作成すれば、拡張 lint をより効率的に利用することができます。makefile を実行して新しいライブラリを作成するには、次のコマンドを入力してください。
% cd /opt/SUNWspro/prod/src/lintlib; make |
ここで、/opt/SUNWspro/prod はインストールディレクトリです。makefile の実行後、lint は /usr/lib ディレクトリ内のライブラリの代わりに拡張モードの新ライブラリを使うようになります。
lint フィルタは、プロジェクト固有のポストプロセッサ (後処理) です。典型的な例では awk スクリプトや類似のプログラムを使用して lint の出力を読み取り、ユーザーのプロジェクトが特に問題ないと判断したメッセージを捨てます。たとえば、時々または常に無視される値を返す文字列関数などです。lint オプションと指令だけでは出力に対して十分な制御が与えられない時は、lint フィルタを使用するとカスタマイズされた診断レポートを作成することができます。
lint の 2 つのオプションはフィルタを開発する際に特に役立ちます。
-s を指定して lint を呼び出すと、複合診断が問題の発生ごとに表示される単純な一行メッセージに変換されます。この解析されたメッセージ書式は awk スクリプトによる分析に適しています。
-k を指定して lint を呼び出すと、ソースファイルに書き込まれたコメントが出力されるので、プロジェクトの決定を文書化したり後処理の動作を指定するのに便利です。コメントが予想される lint メッセージを示していて、報告されたメッセージがそれと同一であった場合、メッセージは除かれます。-k を使用するときは NOTE(LINTED(メッセージ)) 指令をコメントしたいコードの前の行に挿入してください。ここでの メッセージ は、lint が -k を指定して呼び出されたときに出力されるコメントです。
NOTE(LINTED(メッセージ)) のあるファイルに対して -k が使用されない場合の lint の動作については、表 4–9 を参照してください。