プログラムのパフォーマンス解析

ソースコードへの注釈の挿入

注釈とは、ソースコード中に挿入されるひとまとまりの文字列です。注釈を利用することで、ロック lint 自身では推測できないプログラムに関するさまざまな事項をロック lint に伝え、その結果、極端に多量なフラグの出力を抑制したり、ロック lint に一定の条件下でのテストを行わせることができます。注釈は、ドキュメントコードにおいて、一般のコメントとほぼ同様の働きをします。ソースコードへの注釈の挿入には、アサーションと NOTE の 2 種類があります。

注釈は、付録 A 「ロック lint コマンドリファレンス」 で紹介するロック lint サブコマンドのいくつかと同じ働きをします。次の「ソースコードへの注釈の挿入を使用する理由」の項でも述べるように、これらのサブコマンドの代わりにソースコードへの注釈の挿入を利用するほうが一般的に望まれています。

ソースコードへの注釈の挿入を使用する理由

ソースコードへの注釈の挿入を利用するには、いくつかの理由があります。一般的に、注釈はロック lint サブコマンドのスクリプトを利用するよりも望まれています。

注釈のスキーマ

ロック lint はソースコードへの注釈の挿入のスキーマをほかのツールと共有します。Sun WorkShop ANSI C コンパイラをインストールする場合、ロック lint が理解できるすべての注釈名を含むファイル SUNW_SPRO-cc-ssbd も自動的にインストールされます。このファイルは、<installation_directory>/SUNWspro/SC5.0/lib/note に保存されています。

/usr/lib/note 以外の保存場所を指定するには、環境変数 NOTEPATH を以下のように指定してください。


setenv NOTEPATH $NOTEPATH:other_location

NOTEPATH のデフォルト値は以下のとおりです。

<installation_directory/SUNWSPRO/SC5.0/lib/note:/usr/lib/note>

ソースコードへの注釈の挿入を使用するには、ファイル note.h をソースまたはヘッダーファイルに含めておいてください。


#include <note.h>

ロック lint NOTE の利用

ノート形式の注釈の多くは、引数として (ロックまたは変数の) 名前を受け付けます。名前は表 5-5 に示す構文で指定します。

表 5-5 ロック lint NOTE による名前の指定

構文 

意味 

Var

名前付き変数 

Var.Mbr.Mbr...

名前付き struct/union 変数のメンバー 

Tag

このタグを使った、名前未定の struct/union 

Tag::Mbr.Mbr...

名前未定の struct/union のメンバー 

Type

この typedef を使った、名前未定の struct/union 

Type::Mbr.Mbr...

名前未定の struct/union のメンバー 

C では、構造体のタグと型 (Type) は、独立した名前空間に保存されるため、ロック lint に関する限り、2 つの異なる struct が同じ名前を持つことが可能となっています。ロック lint が foo::bar を見つけると、まずはタグ foo の付いた struct を捜します。そして、見つからなかった場合は、Type foo を探し、それが struct を表していることを確認します。

しかし、ロック lint の厳密な動作では、特定の変数またはロックは確実に 1 つの名前によって認識されることが要求されます。そのため、struct に対してタグが提供されず、structtypedef の一部として定義されている場合にのみ、Type が使用されます。

たとえば、以下の例において Foo は型 (Type) の名前として作用します。

typedef struct { int a, b; } Foo;

こうした制限によって、struct について知られた名前は必ず 1 つだけとなります。

名前引数は一般式を受け付けません。たとえば、以下の記述は正しくありません。

NOTE(MUTEX_PROTECTS_DATA(p->lock, p->a p->b))

しかし、注釈の中には (名前以外に) 式を受け入れるものもあります。それらは明確に区別されます。

多くの場合、注釈は名前のリストを引数として受け付けます。リストの要素は空白によって区切られます。リストの指定を単純化するため、こうしたリストを取るすべての注釈は、多くのシェルと同様のジェネレータメカニズムが理解されます。

Prefix{A B ...}Suffix

PrefixSuffix、A、B、... の部分は、空白を含まない文字列ならば何でもかまいません。そのため、上記の表記は以下の表記に相当します。

PrefixASuffix PrefixBSuffix ...

たとえば、以下の表記は、

struct_tag::{a b c d}

次の冗長なテキストと同じ意味となります。

struct_tag::a struct_tag::b struct_tag::c struct_tag::d

この構造は次のように入れ子とすることも可能です。

foo::{a b.{c d} e}

これは以下と同じ意味になります。

foo::a

foo::b.c

foo::b.d

foo::ae

注釈がロックまたはほかの変数を参照する場合、そのロックまたは変数の宣言または定義はすでに出現している必要があります。

データの名前が構造体を表す場合、その構造体のすべての非ロック (相互排他または読み取り側/書き出し側) メンバーを参照します。そうしたメンバーの 1 つが構造体そのものである場合、その非ロックメンバーすべてを意味します。しかしロック lint は条件変数の抽象性を理解するため、それを構成するメンバーへと分けることはありません。

NOTE と _NOTE

NOTE インタフェースは、コンパイル後のオブジェクトコードに影響を与えることなく、ソースコードへのロック lint 用の情報の挿入を可能にします。ノート形式の注釈の基本構文は、以下のどちらかです。

NOTE(NoteInfo)

または

_NOTE(NoteInfo)

そして、_NOTE よりも NOTE のほうが一般的に好まれています。ただし、複数の関連しないプロジェクトにおいて使用されるヘッダーファイルでは、混乱を避けるために _NOTE を使用してください。NOTE がすでに使用され、それを変更したくない場合は、_NOTE を使ってほかのマクロ (ANNOTATION など) を定義してください。たとえば、以下の内容を含むインクルードファイル (annotation.h とします) を定義できます。


#define ANNOTATION _NOTE
#include <sys/note.h>

NOTE インタフェースに渡される NoteInfo は、書式上は以下のいずれかの形式に一致しなければなりません。

NoteName

NoteName(Args)

NoteName は、注釈の種類を示す単なる識別子です。トークンとして正しく、カッコのトークンが適合してさえいれば (閉じカッコが存在する)、Arg は何でもかまいません。それぞれの個別の NoteName は、引数に関して、独自の要求事項を持つことになります。

このマニュアルでは、特にことわり書きがない限り、NOTENOTE_NOTE の両方を指します。

NOTE が使われる場所

NOTE は、ソースコード中の、特定の定義済みの箇所においてのみ有効です。

NOTE が使われるべきでない場所

NOTE() は、前述の場所でのみ使用されます。たとえば、以下の場所での使用は正しくありません。

a = b NOTE(...) + 1;

typedef NOTE(...) struct foo Foo;

for (i=0; NOTE(...) i<10; i++) ...

ノート形式の注釈はステートメントではありません。つまり、NOTE() は、ブロックを構成するために中括弧が使用されない限り、if/else/for/while 本文の内側では使用してはなりません。たとえば、以下の表記は構文エラーを引き起こします。

if (x)

NOTE(...)

データの保護方法

以下の注釈は、関数定義の外側でも内側でも使用可能です。注釈中で記述される名前はすべて、もあらかじめ宣言されている必要があります。

NOTE(MUTEX_PROTECTS_DATA(Mutex, DataNameList))

NOTE(RWLOCK_PROTECTS_DATA(Rwlock, DataNameList))

NOTE(SCHEME_PROTECTS_DATA("description", DataNameList))

最初の 2 つの注釈は、指定されたデータがアクセスされる場合は、必ずロックが保持されるよう、ロック lint に指示します。

3 番目の注釈 SCHEME_PROTECTS_DATA は、データが相互排他ロックも読み取り書き込みロックのいずれも持たない場合に、データをどのように保護するかを説明します。スキーマに対して提供される description はただのテキストであり、プログラム的には重要ではありません。ロック lint は、指定されたデータを完全に無視することで対応します。description の部分は自由に指定できます。

上記の注釈の使用方法を説明するために、いくつかの例を示します。最初の例はとてもシンプルで、ロックが 2 つの変数を保護することを示しています。


mutex_t lock1;
int a,b;
NOTE(MUTEX_PROTECTS_DATA(lock1, a b))

次の例では、さまざまな可能性が示されています。struct foo のメンバーの一部は静的なロックで保護され、ほかのメンバーは foo 上のロックで保護されています。そして、foo のほかのメンバーは、その利用上の規則によって保護されています。


mutex_t lock1;
struct foo {
    mutex_t lock;
    int mbr1, mbr2;
    struct {
        int mbr1, mbr2;
        char* mbr3;
    } inner;
    int mbr4;
};
NOTE(MUTEX_PROTECTS_DATA(lock1, foo::{mbr1 inner.mbr1}))
NOTE(MUTEX_PROTECTS_DATA(foo::lock, foo::{mbr2 inner.mbr2}))
NOTE(SCHEME_PROTECTS_DATA("convention XYZ", inner.mbr3))

1 つのデータは 1 つの手段においてのみ保護が可能です。1 つのデータに対して、保護に関する複数の注釈が使用されている場合 (前記の 3 つ以外だけでなく、READ_ONLY_DATA も含みます)、後の注釈によって自動的に以前の注釈は上書きされます。これによって、1、2 の例外を除く全メンバーが同じ方法で保護されるという構造記述が容易になります。たとえば、以下の例の struct BAR のメンバーのほとんどは struct foo 上のロックによって保護されますが、ただ 1 つだけはグローバルロックによって保護されます。


mutex_t lock1;
typedef struct {
    int mbr1, mbr2, mbr3, mbr4;
} BAR;
NOTE(MUTEX_PROTECTS_DATA(foo::lock, BAR))
NOTE(MUTEX_PROTECTS_DATA(lock1, BAR::mbr3))

読み取り専用変数

NOTE(READ_ONLY_DATA(DataNameList))

この注釈は、関数定義の外側でも内側でも使用可能で、データをどう保護するべきかをロック lint に指示します。また、データは読み取り専用で、書き込み不可であると指示します。


注 -

非可視とみなされている間に、読み取り専用データが書き込まれる場合、エラーは検出されません。ほかのスレッドがデータにアクセスできない場合 (たとえばほかのスレッドがそれを認知していない場合など)、データは非可視とみなされます。


この注釈は、初期化され、その後変更されることが決してないデータと一緒に使用されることがよくあります。ほかのスレッドに対してデータが可視状態となる以前の実行時に初期化が行われる場合は、注釈を使って、その間はデータが非可視であることをロック lint に知らせてください。

ロック lint は、const データが読み取り専用であることを知っています。

非保護読み取りの許可

NOTE(DATA_READABLE_WITHOUT_LOCK(DataNameList))

この注釈は、関数定義の外側でも内側でも使用可能で、保護用のロックを保持することなく、指定されたデータの読み取りが可能であることをロック lint に知らせます。修正するつもりがなければ、非保護データを参照することは不正ではないため、単独で存在する不可分な読み取り可能データ (これに対して、その値がまとめて使用されるデータセットもあります) に対してこの注釈は有用です。

ロックの階層関係

NOTE(RWLOCK_COVERS_LOCKS(RwlockName, LockNameList))

この注釈は、関数定義の外側でも内側でも使用可能で、読み取り書き込みロックとその他のロックのセット間に階層関係が存在することをロック lint に伝えます。こうしたルールの下では、書き込みアクセス用のカバーロックを保持することで、そのカバーされたロックによって保護されるすべてのデータに対するスレッドアクセスを提供します。また、何らかのカバーされたロックを保持する場合、スレッドは読み取りアクセス用のカバーロックを必ず保持しなければなりません。

このように読み取り書き込みロックを使ってほかのロックをカバーする方法は、単に規則の 1 つです。つまり、特殊なロックの種類ではありません。しかし、ロック lint がこうしたカバーリング関係について知らされていないと、ロックは通常の表記方法に従って使用されるとみなしてしまい、その結果、エラーを生成します。

以下の例では、名前未定の foo 構造のメンバー lock が、名前未定の構造 barzot のメンバー lock をカバーしています。

NOTE(RWLOCK_COVERS_LOCKS(foo::lock, {bar zot}::lock))

ロック副作用を持った関数

NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(MutexExpr))

NOTE(READ_LOCK_ACQUIRED_AS_SIDE_EFFECT(RwlockExpr))

NOTE(WRITE_LOCK_ACQUIRED_AS_SIDE_EFFECT(RwlockExpr))

NOTE(LOCK_RELEASED_AS_SIDE_EFFECT(LockExpr))

NOTE(LOCK_UPGRADED_AS_SIDE_EFFECT(RwlockExpr))

NOTE(LOCK_DOWNGRADED_AS_SIDE_EFFECT(RwlockExpr))

NOTE(NO_COMPETING_THREADS_AS_SIDE_EFFECT)

NOTE(COMPETING_THREADS_AS_SIDE_EFFECT)

これらの注釈は、関数定義の外側でも内側でも使用可能です。各注釈は、関数が指定されたロックに対する指定された副作用を持っていること (つまり、関数の終了時において、ロックを関数への進入時とは異なる状態のままに意図的にしておくことなど) をロック lint に伝えます。最後の 2 つの注釈の場合は、副作用はロックについてのものではなく、並行性の状態についてのものとなります。

副作用として読み取り書き込みロックが獲得されることを記述する場合、そのロックが読み取りアクセス用または書き込みアクセス用のどちらかを指定する必要があります。

ロックが読み取り専用アクセス用に獲得された状態から読み取り書き込みアクセス用に獲得された状態に変化する場合、ロックは昇格したといいます。一方、逆方向の変化はロックの降格といいます。

ロック lint は、各関数について、ロックの副作用 (および並行性) を解析します。通常、関数は副作用を持たないとロック lint は考えます。そこで、コードにそうした効果を持たせる場合は、注釈を使ってその意図をロック lint に伝えなければなりません。そして、注釈に指定された副作用とは異なる副作用を持つことが判明すると、エラーメッセージが返されます。

この項で解説している注釈は、だいたい、関数の性質を指すものであり、コードの特定のポイントを指すものではありません。このため、これらの注釈は関数の先頭に記述することをお薦めします。たとえば、以下の 2 つの例の間に違いはありません。上の例の方が読みやすいということだけです。


foo() {
    NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(lock_foo))
    ...
    if (x && y) {
    ...
    }
}


foo() {
    ...
    if (x && y) {
    NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(lock_foo))
    ...
    }
}

関数がそうした副作用を持つ場合、その作用は関数内のどの経路においても同じでなければなりません。ロック lint は、指定された以外の副作用を持つ関数のパスの解析については拒否します。

シングルスレッド化コード

NOTE(COMPETING_THREADS_NOW)

NOTE(NO_COMPETING_THREADS_NOW)

これらの 2 つの注釈は、関数定義の内側でのみ使用可能です。1 つ目の注釈は、コードのこのポイントの後に、このスレッドがアクセスするデータと同じデータにアクセスしようとするほかのスレッドが存在することをロック lint に伝えます。2 つ目の注釈は、もはやそうした状態ではないこと (ほかに実行中のスレッドはない、あるいは、実行中のいかなるスレッドもこのスレッドがアクセスしているデータにアクセスすることはないなど) を指定します。競合するスレッドが存在しなければ、ロック lint は、通常はそのデータを保護するロックを保持することなくそのコードがデータにアクセスするかどうかについて、不具合を報告することはありません。

これらの注釈は、追加のスレッドを 1 つも起動しないうちに、ロックを保持せずにデータを初期化する関数において有用です。こうした関数は、ほかのすべてのスレッドが終了するのを待った後、ロックを保持せずにデータにアクセスすることを許可されます。その具体例を以下に示します。


main() {
    <initialize data structures>
    NOTE(COMPETING_THREADS_NOW)
    <create several threads>
    <wait for all of those threads to exit>
    NOTE(NO_COMPETING_THREADS_NOW)
    <look at data structures and print results>
}


注 -

NOTE が main() に存在する場合、ロック lint は、main() の開始時に、ほかのスレッドが実行中でないと想定します。main()NOTE が含まれない場合は、その想定はしません。


競合スレッドが存在しているとすでに想定されている場合は、解析中に注釈 COMPETING_THREADS_NOW に出会っても、ロック lint は警告を発しません。たとえば、単純な入れ子構造の場合がそうです。注釈は各使用時に異なる意味を示すことができるため、警告メッセージは表示されません (概念的には、スレッドの競合とは、コードの一部分が次のコードと異なる場合を意味します)。一方、先行する COMPETING_THREADS_NOW 注釈 (明示的でも非明示的でも) と一致しない NO_COMPETING_THREADS_NOW 注釈は、警告の原因となります。

到達不可能なコード

NOTE(NOT_REACHED)

この注釈は、関数定義の内側でのみ使用可能で、コードの特定のポイントへの到達が不可能であるため、そのポイントにおいて保持されるロックの条件を無視するように、ロック lint に指示します。この注釈は、たとえば lint の注釈 /* NOTREACHED */ の使用方法のように、exit() に対するすべての呼び出しの後に使用する必要はありません。ただし、この注釈を exit() の定義もしくはそれと同様の定義 (主にロック lint ライブラリ) の中で使用してください。そうすれば、ロック lint は、そうした関数の呼び出しの後のコードには到達できないことがわかります。この注釈は、ロック lint ライブラリの外側ではあまり使用すべきではありません。その使用例 (ロック lint ライブラリ中) を以下に示します。


exit(int code) { NOTE(NOT_REACHED) }

ロックの順序

NOTE(LOCK_ORDER(LockNameList))

この注釈は、関数定義の外側あるいは内側のいずれかで使用可能で、ロックが獲得されるべき順番を指定します。機能的には、assert order および order サブコマンドに類似しています。詳しくは、付録 A 「ロック lint コマンドリファレンス」を参照してください。

デッドロックを避けるために、ロック lint は、同時に複数のロックが保持されなければならない場合、常に、獲得される順序が明確になっていることを前提とします。注釈によってそうした順序がロック lint に知らされている場合、その順序に違反すると、通知メッセージが生成されます。

この注釈は複数回使用されることもありますが、その意味は適切に組み合わされます。たとえば、以下の注釈が与えられた場合、

NOTE(LOCK_ORDER(a b c))

NOTE(LOCK_ORDER(b d))

ロック lint は以下の順序を演繹的に推論します。

NOTE(LOCK_ORDER(a d))

この例では、cd の順序について推論することは不可能です。

この順序の中に循環が存在する場合は、該当するエラーメッセージが生成されます。

ほかのスレッドに対して不可視な変数

NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(DataExpr, ...))

NOTE(NOW_VISIBLE_TO_OTHER_THREADS(DataExpr, ...))

これらの注釈は関数定義の内側でのみ許可され、指定された式によって表される変数がほかのスレッドに対して可視状態であるかどうか、つまりは、ほかのスレッドがこの変数にアクセスできるかどうかをロック lint に伝えます。

また、通常は可視であると想定されている変数でも、この変数へのポインタを持ったスレッドが存在しないため、実際には不可視であることをロック lint に知らせるという注釈の使い方も一般的です。データをヒープの外に割り当てる場合など、こうした状況はしばしば起こります (ほかのスレッドにはまだその変数の構造が不可視であるため、ロックを保持せず安全に構造の初期化を行えます)。


Foo* p = (Foo*) malloc(sizeof(*p));
NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*p))
p->a = bar;
p->b = zot;
NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*p))
add_entry(&global_foo_list, p);

関数の呼び出しが、変数を可視にしたり、不可視にしたりするという副作用を持つことは絶対にありません。関数から戻る時点で、関数が原因となって起こる可視性に関する一切の変化は逆転します。

変数が保護されているという想定

NOTE(ASSUMING_PROTECTED(DataExpr, ...))

この注釈は関数定義の内側でのみ許可され、指定された式によって表される変数が以下に示すいずれかの方法によって保護されていると、この関数が想定しているということをロック lint に伝えます。

これらの条件のいずれも真でない場合、ロック lint はエラーを生成します。


f(Foo* p, Bar* q) {
    NOTE(ASSUMING_PROTECTED(*p, *q))
    p->a++;
    ...
}

ロック lint によって認識されるアサーション

ロック lint は、スレッドやロックに関連したいくつかのアサーションを認識します。(詳細については、「assert」のマニュアルページを参照してください)。

アサーションは、ステートメントが許可されている関数定義の内側でのみ作成が可能です。


注 -

ASSERT() はカーネルおよびドライバコードにおいて利用される一方、assert() はユーザー (アプリケーション) コードで利用されます。ただし、このマニュアルでは、特にことわり書きがない限り、いずれに対しても assert() を使用しています。


全ロックの解放の確認

assert(NO_LOCKS_HELD);

このアサーションによって、コードのこのポイントに到達したなら、このテストを実行するスレッドによってロックは保持されるべきでないと、ロック lint は認識します。このアサーションに対する違反は、解析中に報告されます。ブロックを行うルーチンは、スレッドがブロックまたは終了するときには、保持されるロックがないように、このようなアサーションを使用できます。

また、このアサーションには、獲得されたいかなるロックもその位置では必ず解放させることを、コードを修正しようとする第三者に思い出させる働きもあります。

唯一必要なことは、このアサーションを、ブロックするリーフレベルの関数で使用することです。ブロックするほかの関数を呼び出すためだけに、呼び出す側の関数がブロックするのであれば、呼び出される側の関数のみにアサーションを含めておくだけで呼び出す側に含める必要はありません。そのため、このアサーションは、特にロック lint 用に記述されたライブラリのバージョン (lint ライブラリのように) において (libc など) もっとも頻繁に出現することになるでしょう。

ファイル synch.h は、まだ別途定義されていない場合は、NO_LOCKS_HELD を 1 と定義し、このアサーションが適合するようにします。つまり、実行時にはアサーションは効果的に無視されます。note.h または synch.h のいずれかをインクルードする前に (順番はどちらが先でもかまいません)、NO_LOCKS_HELD を定義することによって、デフォルトの実行時の状態を上書きできます。たとえば、コードの本体に a と b と呼ばれる 2 つのロックだけが使用されている場合は、以下の定義で十分でしょう。


#define NO_LOCKS_HELD (!MUTEX_HELD(&a) && !MUTEX_HELD(&b))
#include <note.h>
#include <synch.h>

このようにしても、ロック lint によるアサーションのテストに影響はありません。ほかのロックが保持されていれば (a と b だけでなく)、ロック lint はその件について報告を行います。

競合スレッドがないことの確認

assert(NO_COMPETING_THREADS);

このアサーションにより、コードのこのポイントに到達したなら、このコードを実行中のスレッドと競合するスレッドがほかにあってはいけないと、ロック lint は認識します。このアサーションに対する違反は (特定の NOTE 形式のアサーションによって提供される情報に基づき)、解析中に報告されます。保護用のロックを保持せずに変数にアクセスする関数は (同じデータにアクセスする関連スレッドはほかにはないとの想定の下で動作しています)、必ずそのようにマークされなければなりません。

デフォルトでは、このアサーションは実行時に無視されます (つまり、いつも成功するということです)。どのスレッドが競合するかという概念にはアプリケーションの情報が含まれるため、NO_COMPETING_THREADS は実行時についての一般的な意味を持つものではありません。たとえば、同じデバイスに対するドライバにおいて、実行中のスレッドがほかに存在しないことを示すため、こうしたアサーションを作成します。一般的な意味ではないため、synch.h は、まだ別途定義されていないならば、NO_COMPETING_THREADS を 1 と定義します。

しかし、note.h または synch.h のいずれかをインクルードする前に (インクルードする順番はどちらが先でもかまいません)、NO_COMPETING_THREADS を定義することによって、デフォルトの意味を上書きできます。たとえば、プログラムが num_threads という変数に実行中のスレッドの数のカウントを保存する場合は、以下の定義で十分です。


#define NO_COMPETING_THREADS (num_threads == 1)
#include <note.h>
#include <synch.h>

このようにしても、ロック lint によるアサーションのテストに影響はありません。

ロック状態のアサート

assert(MUTEX_HELD(lock_expr) && ...);

このアサーションは、カーネル内において広く利用されます。アサーションが有効状態にある場合は、実行時チェックを行います。同じ機能はユーザーコードにも存在します。

このコードは、ロック lint の解析中、有効なアサーションとともにコードが実際に実行されているときほぼ同じことを行います。つまり、実行中のスレッドが指定されたとおりのロックを保持しない場合、エラーを報告します。


注 -

スレッドライブラリは、いずれかのスレッドがロックを取得することをチェックするのみという、緩やかなテストを行います。一方、ロック lint はもっと強力なテストを実行します。


ロック lint は、MUTEX_HELD()RW_READ_HELD()RW_WRITE_HELD()、および RW_LOCK_HELD() マクロの使用、そしてその否定を認識します。こうしたマクロ呼び出しは、&& 演算子によって組み合わせることができます。たとえば、以下のアサーションで、ロック lint は、相互排他ロックが保持されておらず、読み取り書き込みロックが書き込み用に保持されていることをチェックします。


assert(p && !MUTEX_HELD(&p->mtx) && RW_WRITE_HELD(&p->rwlock));

ロック lint は以下のような式も認識します。

MUTEX_HELD(&foo) == 0