3 Dプログラム構文のリファレンス
このリファレンスでは、プローブを有効にして操作を実行するためにDTraceで使用できるDプログラムの記述方法について説明します。
プログラム構造
Dプログラムは、有効にするプローブを記述する一連の節、実行のタイミングを制御するオプションの述語、および通常はプローブの起動時に実装する機能を記述する1つ以上の文で構成されます。Dプログラムには、変数の宣言と新しい型の定義を含めることもできます。プローブ節の宣言では、次の構造体が使用されます:
probe descriptions
/ predicate /
{
statements
}
- プローブ記述
-
プローブの説明は、プローブの完全な記述を観念的に表し、次の形式を取ります:
provider:module:function:name
フィールド記述子の定義は次のとおりです:
- provider
-
プローブが属するDTraceプロバイダの名前。
- module
-
プローブが特定のプログラムの場所に対応している場合は、そのプローブがあるカーネル・モジュール、ライブラリまたはユーザー空間プログラムの名前。一部のプローブは、より抽象的なトレース・ポイントに関連する場合、特定のソースの場所に拘束されないモジュール名に関連付けられていることがあります。
- function
-
プローブが特定のプログラムの場所に対応している場合は、そのプローブがあるプログラム関数の名前。
- name
-
プローブのセマンティックな意味がわかる名前(
BEGIN
やEND
など)。
DTraceは、プローブを参照するときに短縮形を認識します。慣例により、プローブ記述の一部のフィールドが指定されていない場合、DTraceは、指定されている名前部分の値と一致するすべてのプローブにリクエストを一致させます。たとえば、プロバイダ、モジュールおよび関数のフィールドの値とは無関係に、名前フィールドが
BEGIN
のすべてのプローブと一致するように、スクリプト内でプローブ名BEGIN
を参照できます。たとえば、次のように参照されるプローブが見つかることがあります:BEGIN
プローブがDプログラム内で参照されていて、完全なプローブ記述を使用していない場合、フィールドは優先順位に基づいて解釈されます:
- 次のように表された単一の構成要素は、プローブ名に一致します:
name
- 次のように表された2つの構成要素は、関数とプローブ名に一致します:
function:name
- 3つの構成要素は、モジュール、関数、プローブ名に一致します
module:function:name
プローブはIDで参照することもできますが、この値は時間の経過によって変化する可能性があります。新しいプロバイダ・モジュールはいつでもロードでき、新しいプローブをオンザフライで作成できるプロバイダもあるため、システム上のプローブの数はIDと直接相関しません。数値のプローブIDを使用したプローブの参照は避けてください。
プローブ記述では、シェルのグロブというパターン・マッチング構文(
sh(1)
マニュアル・ページを参照)と類似したパターン・マッチング構文もサポートされます。たとえば、次の記述のようにアスタリスク記号(*)を使用すると、ワイルドカード一致を実行できます:sdt:::tcp*
プローブ記述に空白のフィールドがある場合、そのフィールドに対してワイルドカード一致が実行されます。
意図的に複数のプローブを一致させる場合を除いて、予測不可能な結果を回避するために、完全なプローブ記述を指定するようにお薦めします。
表3-1 プローブ名のパターン・マッチング文字
記号 説明 *
NULL文字列を含む、任意の文字列に一致します。
?
任意の1文字を検索します。
[]
大カッコ内のいずれかの文字と一致します。文字のペアを
-
で区切ると、そのペア間(両端の文字を含む)の任意の文字に一致します。[
の後ろの最初の文字を!
にすると、カッコ内に含まれない任意の文字と一致します。\
次の文字に特別な意味を持たせず、文字どおりに解釈します。
プローブが正常に一致して有効化されるには、完全なプローブ記述で、すべてのフィールドが一致する必要があります。パターンではないプローブ記述フィールドは、それに対応するプローブのフィールドと完全に一致する必要があります。空の記述フィールドは、すべてのプローブと一致する点に注意してください。
複数のプローブはコンマ区切りリストに含めることができます。複数のプローブを記述に含めることで、各プローブがアクティブ化されるときに同じ述語と関数のシーケンスが適用されます。
- 述語
-
述語は、スラッシュのペア(
//
)の間に現れる式で、関連する関数の処理が必要かどうかを判断するためにプローブの起動時に評価されます。述語は、Dプログラムでより複雑な制御フローを構築するために使用する主要な条件構成です。プローブ節の述語部分は、どのプローブでも完全に省略できます。この場合、プローブの起動時に常に関数が処理されるようになります。述語式では、すべてのD演算子を使用できます。また、変数や定数などの任意のDデータ・オブジェクトを含めることができます。述語式は、真または偽の結果を導き出すため、整数型またはポインタ型の値に評価される必要があります。すべてのD式と同じく、ゼロ値は偽、ゼロ以外の値は真と解釈されます。
- 文
-
文は、セミコロン(
;
)で区切られ、中カッコ({}
)で囲まれた式または関数のリストによって記述されます。文が含まれていない空の中カッコのセットでは、デフォルト・アクションが処理されます。デフォルト・アクションは、プローブのアクティブ化を報告します。
プログラムは、複数のプローブ節の宣言で構成できます。節はプログラム順に実行されます。
プログラムはファイル・システムに格納して、DTraceユーティリティで実行できます。プログラムを実行可能スクリプトに変換するには、ファイルの先頭にdtraceコマンドを呼び出すインタプリタ・ディレクティブと、プログラムの実行に必要なオプションを1つの引数として追加します。スクリプトの先頭にインタプリタ行を追加する際の詳細は、sh(1)
マニュアル・ページを参照してください。インタプリタ・ディレクティブは次のようになります:
#!/usr/sbin/dtrace -qs
スクリプトには、実行時オプションとコンパイラ・オプションを設定するためのDプラグマ指令を含めることもできます。この情報をスクリプトに含める方法の詳細は、「DTraceの実行時およびコンパイル時のオプション・リファレンス」を参照してください。
型、演算子および式
Dには、各種のデータ・オブジェクトにアクセスして操作できる機能があります。具体的には、変数やデータ構造の作成または変更、OSカーネルやユーザー・プロセスで定義されているデータ・オブジェクトへのアクセス、整数定数、浮動小数点定数、文字列定数の宣言などを実行できます。Dには、オブジェクトの操作や複合式の作成に使用されるANSI C演算子のスーパーセットが用意されています。この項では、一連の型、演算子および式のルールの詳細を説明します。
識別子名およびキーワード
D識別子名は、アルファベットの大文字と小文字、数字、および下線で構成されます。先頭文字はアルファベットまたは下線である必要があります。先頭文字が下線(_
)の識別子名はすべて、Dシステム・ライブラリ用に予約されています。これらの名前はDプログラム内での使用を避けてください。慣例により、Dプログラムでは、通常、変数名にはアルファベットの大/小文字、定数名にはすべて大文字を使用します。
D言語のキーワードは、プログラミング言語の構文自体で使用するために予約されている特別な識別子です。これらの名前は常にアルファベットの小文字のみで指定し、D変数名には使用しないでください。次の表に、D言語で使用するために予約されているキーワードをリストします。
表3-2 Dのキーワード
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dでは、ANSI Cキーワードのスーパーセットがキーワードとして予約されています。将来D言語で使用するために予約されているキーワードは、*
でマークされています。将来の使用のために予約されているキーワードを使用しようとすると、Dコンパイラは構文エラーを生成します。Dでは定義されているがANSI Cでは定義されていないキーワードは、+
でマークされています。Dには、ANSI Cの型および演算子の完全セットが用意されています。Dプログラミングの主な違いは、制御フロー構造体が存在しない点です。ANSI Cで制御フローに関連付けられたキーワードは、Dでは将来使用するために予約されています。
データ型およびサイズ
Dには、整数および浮動小数点の定数の基本データ型があります。算術演算は、Dプログラム内の整数に対してのみ実行可能です。Dでは、データ構造を初期化するために浮動小数点定数を使用できますが、浮動小数点算術演算の使用は許可されていません。Dには、プログラムの作成に使用する64ビットのデータ・モデルが用意されています。
次の表に、64ビットのデータ・モデルの整数型名とサイズを示します。整数は、常にシステム・ネイティブのバイト・エンコーディング順序で2の補数形式で表されます。
表3-3 Dの整数データ型
型名 | 64ビットのサイズ |
---|---|
|
1バイト |
|
2バイト |
|
4バイト |
|
8バイト |
|
8バイト |
整数型(char
を含む)の前には、signedまたはunsigned修飾子を付けられます。unsigned修飾子の指定がない場合、整数は暗黙的にsignedになります。Dコンパイラでは、次の表に示す型の別名も用意されています。
表3-4 Dの整数型の別名
型名 | 説明 |
---|---|
|
1バイトの符号付き整数 |
|
2バイトの符号付き整数 |
|
4バイトの符号付き整数 |
|
8バイトの符号付き整数 |
|
ポインタと同じサイズの符号付き整数 |
|
1バイトの符号なし整数 |
|
2バイトの符号なし整数 |
|
4バイトの符号なし整数 |
|
8バイトの符号なし整数 |
|
ポインタと同じサイズの符号なし整数 |
これらの型の別名は、前の表にリストされた対応する基本型の名前の使用と同義であり、データ・モデルごとに適切に定義されています。たとえば、uint8_t
の型名は、符号なしchar
型の別名です。
ノート:
事前定義された型の別名は、プリプロセッサによって含められるファイルでは使用できません。
Dの浮動小数点型は、ANSI Cの宣言および型と互換性があります。Dでは、浮動小数点演算子はサポートされていませんが、浮動小数点型データ・オブジェクトのトレースとprintf
関数によるフォーマットができます。次の表に示す浮動小数点型を使用できます。
表3-5 Dの浮動小数点データ型
型名 | 64ビットのサイズ |
---|---|
|
4バイト |
|
8バイト |
|
16バイト |
Dには、ASCII文字列を表す特殊なstring
型も用意されています。文字列の詳細は、「DTraceの文字列処理」を参照してください。
定数
整数定数は、10進法(12345
)、8進法(012345
)または16進法(0x12345
)の形式で記述できます。8進法(base 8)の定数には、接頭辞として0を付ける必要があります。16進法(base 16)の定数には、接頭辞として0x
または0X
を付ける必要があります。整数定数には、その値を表す型として、int
、long
およびlong long
のうち最も小さい型が割り当てられます。値が負である場合、型の符号付きバージョンが使用されます。値が正で、符号付きの型で表すには値が大きすぎる場合は、符号なしの型が使用されます。Dの型であることを明示的に指定するには、任意の整数定数に、次の表にリストされた接尾辞のいずれかを適用します。
接尾辞 | Dの型 |
---|---|
|
コンパイラによって選択された型の |
|
|
|
|
|
|
|
|
浮動小数点定数は常に10進形式で作成します。さらに、小数点(12.345
)か指数(123e45
)、またはその両方(123.34e-5
)を含める必要があります。浮動小数点定数には、デフォルトでdouble
型が割り当てられます。Dの型であることを明示的に指定するには、任意の浮動小数点定数に、次の表にリストされた接尾辞のいずれかを適用します。
接尾辞 | Dの型 |
---|---|
|
|
|
|
文字定数は単一の文字として記述するか、単一引用符の内側のエスケープ・シーケンス('a'
)として記述します。文字定数には、char
ではなくint
型が割り当てられます。文字定数は、ASCII文字セット内のその文字の値によって値が決定される整数定数と同等です。文字とその値のリストについては、ascii(7)
のマニュアル・ページを参照してください。次の表に示す特殊なエスケープ・シーケンスを使用することもできます。Dでは、ANSI Cと同じエスケープ・シーケンスを使用します。
表3-6 文字エスケープ・シーケンス
エスケープ・シーケンス | 表現 | エスケープ・シーケンス | 表現 |
---|---|---|---|
|
アラート |
|
円記号 |
|
バックスペース |
|
疑問符 |
|
改ページ |
|
一重引用符 |
|
改行 |
|
二重引用符 |
|
キャリッジ・リターン |
|
8進数値0oo |
|
水平タブ |
|
16進数値0xhh |
|
垂直タブ |
|
NULL文字 |
複数の文字指定子を1組の一重引用符で囲むことにより、個々のバイトが対応する文字指定子に応じて初期化された整数を作成できます。これらのバイトは、文字定数の左から右の順に読み取られ、オペレーティング環境のネイティブのエンディアンに対応する順序で結果の整数に割り当てられます。1つの文字定数に、最大8個の文字指定子を含めることができます。
文字列定数は、二重引用符で囲むことにより、任意の長さで作成できます("hello"
)。文字列定数には、リテラルの改行文字を含めることはできません。改行が含まれる文字を作成するには、リテラルの改行文字を入力するかわりに、\n
というエスケープ・シーケンスを使用します。文字列定数には、前述の文字定数について示した特殊文字のエスケープ・シーケンスを含めることができます。ANSI Cの場合と同様に、文字列は、NULL文字(\0
)で終わる文字の配列として表されます。このNULL文字は、文字列定数を宣言するたびに暗黙的に追加されます。文字列定数には、Dの特殊なstring
型が割り当てられます。Dコンパイラには、文字列として宣言された文字配列の比較とトレースを行う特別な機能セットが用意されています。
算術演算子
次の表に、二項算術演算子を示します。これらの演算子が整数に対して持つ意味はすべて、ANSI Cの場合と同じです。
表3-7 二項算術演算子
演算子 | 説明 |
---|---|
|
整数の加算 |
|
整数の減算 |
|
整数の乗算 |
|
整数の除算 |
|
整数の剰余演算 |
Dの算術演算は、整数オペランドまたはポインタに対してのみ実行できます。Dプログラムでは、浮動小数点オペランドに対する算術演算は実行できません。DTraceの実行環境では、整数のオーバーフローまたはアンダーフローに対して一切処理されません。オーバーフローやアンダーフローが発生する状況では、それらの条件をチェックする必要があります。
ただし、DTrace実行環境では、/
および%
演算子の誤用によるゼロ除算エラーが自動的に確認され、レポートされます。コンパイル時に検出可能な無効な除算操作がDプログラムに含まれていると、コンパイル・エラーが返され、コンパイルは失敗します。無効な除算操作が実行時に行われると、現在の節の処理は終了し、ERRORプローブがアクティブ化されます。DプログラムにERRORプローブの節がない場合、エラーが出力され、トレースは続行されます。それ以外の場合は、ERRORプローブに割り当てられた節内のアクションが処理されます。DTraceによって検出されるエラーは、別のDTraceユーザーやOSカーネルには影響しません。そのため、こうしたエラーのいずれかがDプログラムに誤って含まれている場合に発生する損害について懸念する必要はありません。
これらの二項演算子に加えて、+
演算子と-
演算子は単項演算子として使用することもできます。これらの演算子は、どの二項算術演算子よりも優先されます。すべてのD演算子の優先順位と結合性プロパティの詳細は、「演算子の優先順位」を参照してください。優先順位を制御するには、式を丸カッコ(()
)で囲んでグループ化します。
関係演算子
次の表に、二項関係演算子を示します。これらの演算子が持つ意味はすべて、ANSI Cの場合と同じです。
表3-8 Dの関係演算子
演算子 | 説明 |
---|---|
|
左のオペランドは右のオペランドより小さい |
|
左のオペランドは右のオペランド以下 |
|
左のオペランドは右のオペランドより大きい |
|
左のオペランドは右のオペランド以上 |
|
左のオペランドは右のオペランドと等しい |
|
左のオペランドは右のオペランドと等しくない |
関係演算子は、Dの述語の記述で最も頻繁に使用されます。各演算子は、int
型の値(条件がtrue
の場合は1、false
の場合は0)に評価されます。
関係演算子は、整数、ポインタまたは文字列のペアに適用できます。ポインタ同士を比較した場合、2つのポインタの整数を符号なし整数とみなして比較した場合と同じ結果になります。文字列同士を比較した場合、結果は、2つのオペランドに対してstrcmp()
を実行した場合と同じになります。次の表は、Dの文字列比較とその結果の例を示しています。
Dの文字列比較 | 結果 |
---|---|
|
1 ( |
|
1 ( |
|
0 ( |
関係演算子は、列挙型に関連付けられたデータ・オブジェクトと、列挙によって定義された列挙子タグとの比較に使用することもできます。
論理演算子
次の表に、二項論理演算子を示します。最初の2つの演算子は、対応するANSI C演算子と同じものです。
表3-9 Dの論理演算子
演算子 | 説明 |
---|---|
|
論理演算子 |
|
論理演算子 |
|
論理演算子 |
論理演算子は、Dの述語の記述で最も頻繁に使用されます。論理演算子AND
は、左のオペランドがfalseの場合に右の式は評価しないという短絡評価を実施します。論理演算子OR
も、左のオペランドがtrueの場合に右の式は評価しないという短絡評価を実施します。論理演算子XOR
は、短絡を実施しません。両方の式のオペランドが常に評価されます。
バイナリ論理演算子の他に、単項演算子!
を使用して、単一のオペランドの論理否定を実行することもできます。つまり、0オペランドを1に、0以外のオペランドを0に変換します。慣例により、Dプログラムでは、Boolean値を表す整数を操作するときには!
、Boolean以外の整数を操作するときには== 0
を使用しますが、これらの式は同等です。
論理演算子は、整数型またはポインタ型のオペランドに適用できます。論理演算子は、ポインタ・オペランドを符号なしの整数値とみなします。Dのすべての論理演算子および関係演算子の場合と同じく、0以外の整数値を持つオペランドは真、整数値0を持つオペランドは偽です。
ビット演算子
Dには、整数オペランド内の個々のビットを操作するために、次の表に示す次のビット演算子が用意されています。これらの演算子が持つ意味はすべて、ANSI Cの場合と同じです。
表3-10 Dのビット演算子
演算子 | 説明 |
---|---|
|
単一のオペランドのビットごとの否定を実行するために使用できる単項演算子: オペランドの各0ビットを1ビットに変換し、オペランドの各1ビットを0ビットに変換します |
|
ビット単位の |
|
ビット単位の |
|
ビット単位の |
|
右のオペランドに指定されたビット数だけ左のオペランドを左にシフトする |
|
右のオペランドに指定されたビット数だけ左のオペランドを右にシフトする |
シフト演算子を使用すると、特定の整数オペランド内のビットを左右に移動できます。左へシフトすると、結果の右側にある空のビット位置にゼロが入力されます。符号なし整数オペランドを使用して右へシフトすると、結果の左側の空のビット位置にゼロが入力されます。符号付き整数オペランドを使用して右へシフトすると、左側の空のビット位置に符号ビットの値が入力されます。この操作は、算術シフト演算とも呼ばれます。
ビット数が負の値である場合や、左側のオペランド自体のビット数より大きい場合、整数値をシフトすると、未定義の結果が得られます。この条件がDプログラムのコンパイル時に検出されると、Dコンパイラによりエラー・メッセージが生成されます。
代入演算子
次の表に、二項代入演算子を示します。変更できるのは、Dの変数と配列のみです。カーネル・データ・オブジェクトと定数は、Dの代入演算子を使用して変更することはできません。代入演算子が持つ意味は、ANSI Cの場合と同じです。
表3-11 Dの代入演算子
演算子 | 説明 |
---|---|
|
左のオペランドを右の式の値と等しくする |
|
左のオペランドを右の式の値の分だけ増分する |
|
左のオペランドを右の式の値の分だけ減分する |
|
左のオペランドに右の式の値を掛ける |
|
左のオペランドを右の式の値で割る |
|
左のオペランドを右の式の値で割ったときの剰余を求める |
|
左のオペランドと右の式の値のORをビット単位で計算する |
|
左のオペランドと右の式の値のANDをビット単位で計算する |
|
左のオペランドと右の式の値のXORをビット単位で計算する |
|
右の式の値に指定されたビット数だけ左のオペランドを左にシフトする |
|
右の式の値に指定されたビット数だけ左のオペランドを右にシフトする |
=
以外の代入演算子は、以前に説明した他の演算子のいずれかと=
演算子を組み合せて使用するための省略形として用意されています。たとえば、式x = x + 1
は、式x += 1
と同じです。これらの代入演算子は、以前に説明したバイナリ形式のオペランド型と同じルールに準拠します。
代入演算子の結果は、左側の式の新しい値と同等の式になります。代入演算子やこれまでに説明した他の演算子を組み合せて使用すると、任意の複合式を表現できます。丸カッコ()
を使用すると、複合式の項をグループ化できます。
インクリメント演算子およびデクリメント演算子
++
および--
が用意されています。これらの演算子が持つ意味は、ANSI Cの場合と同じです。これらの演算子は、変数に適用することも、構造体、共用体または配列の個々の要素に適用することもできます。各演算子は、変数名の前または後に適用できます。演算子が変数名の前にある場合は、まず変数が変更され、結果の式は新しい変数の値と等しくなります。たとえば、次の2つのコード部分の結果は同じです。
x += 1; y = x;
y = ++x;
y = x; x -= 1;
y = x--;
インクリメント演算子とデクリメント演算子を使用すると、変数を宣言せずに新しい変数を作成できます。変数宣言を省略して、変数にインクリメント演算子またはデクリメント演算子を適用すると、この変数の型は暗黙的にint64_t
として宣言されます。
インクリメント演算子とデクリメント演算子を配列または構造体の要素に使用する場合は、要素への完全な参照の後または前に演算子を配置します。
int foo[5];
struct { int a; } bar;
bar.a++;
foo[1]++;
--foo[1];
インクリメント演算子とデクリメント演算子は、整数変数またはポインタ変数に適用できます。これらの演算子を整数変数に適用すると、それに対応する値は1ずつ増分または減分されます。これらの演算子をポインタ変数に適用すると、ポインタが参照しているデータ型のサイズ単位でポインタ・アドレスが増分または減分されます。
条件式
Dには、if-then-else
構造を使用する機能がありません。かわりに、3項演算子(?:
)を使用した条件式が、この機能の一部を模倣するために使用できます。3項演算子は、3つの式を関連付けます。最初の式を使用することで、残りの2つの式のいずれかを条件付きで評価します。
たとえば、次のD文を使用して、i
の値に応じて2つの文字列のいずれかに変数x
を設定できます。
x = i == 0 ? "zero" : "non-zero";
前述の例では、最初に式i == 0
が評価され、それがtrueであるかfalseであるかが判断されます。この式が真である場合、2番目の式が評価され、その値が返されます。この式が偽である場合、3番目の式が評価され、その値が返されます。
その他のD演算子と同様に、単一の式で複数の?:
演算子を使用することで、より複雑な式を作成できます。たとえば、次の式は、0-9
、a-f
またはA-F
のいずれかの文字を含むchar
変数c
をとり、この文字を数字とみなした場合の値を16進数(base 16)の整数として返します。
hexval = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c + 10 - 'a' : c + 10 - 'A';
真の値と評価されるために、?:
が使用されている最初の式は、ポインタまたは整数であることが必要です。2番目と3番目の式は、互換性のある型であれば何でもかまいません。一方が文字列を返し、もう一方が整数を返すような条件式は構成できません。2番目と3番目の式は、値を持つtrue式であることが必要です。そのため、こうした式では値を返さないデータ・レポート関数は使用できません。条件付きでデータをトレースする場合は、かわりに述語を使用します。
型変換
式を構成する際に使用するオペランドの型が違っていても互換性がある場合、型変換が実行されて、結果として生じる式の型が決まります。Dの型変換のルールは、ANSI Cの整数の算術変換ルールと同じです。多くの場合、これらのルールは通常の算術変換と呼ばれます。
各整数型は、char
、short
、int
、long
、long long
の順序でランク付けされます。それに対応する符号なし型には、その型の符号付きよりも高いランクが割り当てられますが、その次の整数型よりも低いランクが割り当てられます。2つの整数オペランドを使用してx + y
のような式を構成するときに、これらのオペランドの型が異なる場合、ランクが高い方のオペランド型が結果の型として使用されます。
変換が必要な場合は、ランクが低い方のオペランドが最初に、ランクの高い方の型に昇格されます。昇格によってオペランドの値が変更されることはありません。符号に従ってより大きいコンテナに値が拡張されるのみです。符号なしオペランドが昇格される場合、結果として生じる整数の未使用の上位ビットにゼロが入力されます。符号付きオペランドが昇格される場合、符号の拡張を実行することによって未使用の上位ビットが入力されます。符号付き型を符号なし型に変換すると、符号付き型は最初に符号拡張されてから、変換によって決定された新しい符号なし型が割り当てられます。
整数などの型は、別の型に明示的にキャストすることもできます。ポインタと整数は、任意の整数型またはポインタ型にキャストできます。ただし、別の型にはキャストできません。
整数またはポインタをキャストする場合、次のような式を使用します。
y = (int)x;
この例では、丸括弧で囲んだ変換先の型を元の式の接頭辞として使用しています。整数をよりランクの高い型にキャストするには、昇格を実行します。整数をよりランクの低い型にキャストするには、不要な整数の上位ビットをゼロにします。
Dでは、浮動小数点の算術演算が含まれていません。そのため、浮動小数点オペランドの変換やキャストは許可されていません。また、暗黙的な浮動小数点変換のルールも定義されていません。
演算子の優先順位
Dには、演算子の優先順位と結合性の複雑なルールが含まれています。このルールは、ANSI Cの演算子の優先順位ルールとの正確な互換性を提供します。次の表の次の項目は、上から優先順位の高い順に並べてあります。
表3-12 D演算子の優先順位および結合性
演算子 | 結合性 |
---|---|
|
左から右 |
|
右から左 (これらは単項演算子である点に注意してください) |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
右から左 |
|
右から左 |
|
左から右 |
この表にリストされているカンマ(,
)演算子は、ANSI Cのカンマ演算子との互換性用です。一連の式を左から右の順に評価し、右端の式の値を返すために使用できます。この演算子はCとの互換性のために用意されているもので、使用しないことをお薦めします。
演算子の優先順位の表にリストされた()
は、関数コールを表します。Dでは、カンマを使用して、関数の引数を列挙したり、連想配列キーのリストを作成することもできます。このカンマはカンマ演算子とは別のもので、左から右の順で評価される保証はありません。Dコンパイラが関数の引数を評価する順番や、連想配列のキーを評価する順番は、特に決まっていません。このようなコンテキストでは、式i
とi++
のペアなど、相互の副作用がある式の使用に注意してください。
演算子の優先順位の表にリストされた[]
は、配列または連想配列の参照を表します。集積体は連想配列としても処理されることに注意してください。[]
演算子は、固定サイズのC配列に索引を付けるために使用することもできます。
次の表では、D言語で提供されるその他の演算子の機能について詳しく説明します。
演算子 | 説明 |
---|---|
|
オブジェクトのサイズを計算します。 |
|
型のメンバーのオフセットを計算します。 |
|
オペランドを文字列に変換します。 |
|
データ型を変換します。 |
単項の |
オブジェクトのアドレスを計算します。 |
単項の |
オブジェクトへのポインタを間接参照します。 |
|
構造体または共用体の型のメンバーにアクセスします。 |
型と定数の定義
この項では、Dで型の別名と名前付き定数を宣言する方法について説明します。また、Dの型とプログラムおよびOSの型と識別子の名前空間の管理についても説明します。
typedef
typedef
は、既存の型の別名として識別子を宣言するために使用します。typedef
宣言は、次の形式でプローブ節の外側で使用します:
typedef existing-type new-type ;
existing-typeは任意の型の宣言であり、new-typeはこの型の別名として使用する識別子です。たとえば、Dコンパイラで次の宣言を内部使用し、uint8_t
型の別名を作成します。
typedef unsigned char uint8_t;
型の別名は、標準の型が使用可能な任意の場所で使用できます。たとえば、変数、連想配列値、またはタプルのメンバーの型として使用できます。また、typedef
は、次の例のように、新しいstruct
の定義など、より綿密な宣言と組み合せることもできます。
typedef struct foo {
int x;
int y;
} foo_t;
前述の例では、struct foo
は、その別名foo_t
と同じ型を使用して定義されています。LinuxのCシステム・ヘッダーではしばしば、接尾辞_t
を使用してtypedef
の別名を表します。
列挙
プログラム内の定数のシンボル名を定義すると、プログラムが読みやすく、将来的に管理しやすくなります。その方法の1つとして、列挙を定義します。これにより、一連の整数と、列挙子と呼ばれる一連の識別子を関連付けます。この列挙子が認識され、対応する整数値に置き換えられます。列挙は、次のような宣言を使用して定義します。
enum colors {
RED,
GREEN,
BLUE
};
この列挙の最初の列挙子RED
には値ゼロが割り当てられ、後続の列挙子にはそれぞれ次の整数値が割り当てられます。
任意の列挙子に明示的に整数値を指定することもできます。これを行うには、次の例のように、列挙子の後ろに等号と整数定数を付けます。
enum colors {
RED = 7,
GREEN = 9,
BLUE
};
列挙子BLUE
には値10
が割り当てられます。これは、値が指定されておらず、前の列挙子が9
に設定されているためです。列挙が定義されたら、整数定数が使用される場所であればDプログラム内のどこでも列挙子を使用できます。さらに、列挙enum colors
は、int
と同等の型としても定義されます。Dコンパイラは、int
を使用できる場所であればどこでもenum
型の変数の使用を許可します。また、enum
型の変数には任意の整数値を代入できます。型名が不要な場合は、宣言内でenum
名を省略することもできます。
列挙子はプログラム内で後続するすべての節と宣言で参照可能です。そのため、複数の列挙で同じ列挙子識別子を定義することはできません。ただし、1つの列挙または複数の列挙内に同じ値を持つ列挙子を複数定義することは可能です。また、対応する列挙子のない整数を列挙型の変数に割り当てることもできます。
Dの列挙の構文は、対応するANSI Cの構文と同じです。Dでも、OSカーネルとそのロード可能なモジュールで定義された列挙にアクセスできます。こうした列挙子は、Dプログラムではグローバルに参照可能でないことに注意してください。カーネル列挙子が表示されるのは、対応する列挙型のオブジェクトとの比較を行う際に引数として指定する場合のみです。この機能により、OSカーネルで定義されている膨大な列挙のコレクションとの識別子名の不用意な衝突からDプログラムを保護します。
インライン
Dの名前付き定数は、inline
ディレクティブを使用して定義することもできます。このディレクティブを使用すると、より一般的な方法で、コンパイル時に事前に定義された値や式で置き換えられる識別子を作成できます。インライン・ディレクティブは、Cプリプロセッサで提供される#define
ディレクティブよりも強力な語彙置換形式です。これは、置換には実際の型が割り当てられ、一連の語彙トークンではなくコンパイルされた構文ツリーを使用して置換を実行できるためです。inline
ディレクティブは、次の形式の宣言を使用して指定します。
inline type name = expression;
typeは既存の型の型宣言です。nameはインラインまたはグローバル変数としてまだ定義されていない任意の有効なD識別子です。expressionは任意の有効なD式です。インライン・ディレクティブの処理後、プログラム・ソース内のnameの後続の各インスタンスが、コンパイルされた形式expressionで置き換えられます。
たとえば、次のDプログラムでは、文字列"hello
"と整数値123
がトレースされます。
inline string hello = "hello";
inline int number = 100 + 23;
BEGIN
{
trace(hello);
trace(number);
}
インライン名は、対応する型のグローバル変数が使用される場所であればどこでも使用できます。コンパイル時に、インライン式が整数定数または文字列定数に評価された場合、スカラー配列次元など、定数式を必要とする状況でもインライン名を使用することもできます。
インライン式は、ディレクティブの評価の一環として構文エラーについてチェックされます。式の結果の型は、D代入演算子(=
)に使用されるルールに従って、inline
で定義される型と互換性を持つ型になることが必要です。インライン式ではinline
識別子自体を参照できません。再帰的定義は許可されていません。
DTraceソフトウェア・パッケージは、システム・ディレクトリ/usr/lib64/dtrace/installed-version
に、Dプログラムで使用可能なインライン・ディレクティブが含まれている複数のDソース・ファイルをインストールします。
たとえば、signal.d
ライブラリには、次の形式のディレクティブが含まれます。
inline int SIGHUP = 1;
inline int SIGINT = 2;
inline int SIGQUIT = 3;
...
これらのインライン定義を使用すると、sigaction(2)
のマニュアル・ページに記載されている現在のOracle Linuxのシグナル名セットにアクセスできます。同様に、errno.d
ライブラリには、errno(3)
のマニュアル・ページに記載されているCのerrno
定数のインライン・ディレクティブが含まれます。
デフォルトでは、Dコンパイラには、提供されているすべてのDライブラリ・ファイルが自動的に含まれるため、これらの定義はすべてのDプログラムで使用できます。
型の名前空間
ANSI Cなどの従来の言語では、型の可視性は、型が関数や別の宣言の内側でネストされているかどうかによって決まります。Cプログラムの外部スコープで宣言された型には、単一のグローバル名前空間が関連付けられるため、これらの型は、プログラム全体にわたって表示されます。通常、この外部スコープには、Cヘッダー・ファイルに定義された型が含まれます。こうした言語とは異なり、Dでは複数の外部スコープから型にアクセスできます。
Dは、OSカーネル、関連するロード可能なカーネル・モジュールのセット、システム上で実行されるユーザー・プロセスなど、ソフトウェア・スタックの異なるレイヤーにわたる動的な可観測性を提供する言語です。単一のDプログラムにより、プローブをインスタンス化し、複数のカーネル・モジュールや、その他の独立したバイナリ・オブジェクトにコンパイルされたソフトウェア・エンティティからデータを収集できます。そのため、DTraceやDコンパイラで使用可能な型の空間には、定義が異なっていることもある同じ名前の複数のデータ型が存在する可能性があります。このような状況を管理するために、各型には、それを含むプログラム・オブジェクトによって識別される名前空間が関連付けられます。メイン・カーネルやカーネル・モジュールなど、特定のカーネル・レベル・オブジェクトの型にアクセスするには、任意の型名にオブジェクト名と逆引用符(`
)のスコープ演算子を指定します。
foo
という名前のカーネル・モジュールに次のC型宣言が含まれている場合:
typedef struct bar {
int x;
} bar_t;
Dからstruct bar
やbar_t
型にアクセスするには、次のような型名を使用します。
struct foo`bar
foo`bar_t
たとえば、カーネルにinclude/linux/sched.h
で記述されたtask_struct
が含まれているとします。この構造体の定義は、ビルド時のカーネル構成に応じて異なります。この構造体に関する情報(サイズなど)は、次のように参照することで確認できます:
sizeof(struct vmlinux`task_struct)
逆引用符の演算子は、型名が適切であれば、どのような状況でも使用できます。たとえば、D変数宣言の型や、Dプローブ節のキャスト式を指定する場合などです。
Dコンパイラには、名前CとDを使用する2つの特殊な組込み型の名前空間も用意されています。Cの型の名前空間には、最初にint
などのANSI C固有の標準型が移入されます。また、dtrace -Cコマンドを実行することでCプリプロセッサ(cpp)を使用して得られた型定義がCスコープで処理されて追加されます。そのため、コンパイル・エラーの発生なしに、別の型の名前空間ですでに参照可能な型宣言が含まれるCヘッダー・ファイルを組み込むことができます。
Dの型の名前空間には、最初に、int
やstring
などのD固有の型と、uint64_t
などのDの組込み型の別名が移入されます。Dプログラム・ソース内で使用される新しい型宣言はすべて、Dの型の名前空間に自動的に追加されます。Dプログラム内で、他の名前空間のメンバーの型から構成される複合型(struct
など)を作成すると、これらのメンバーの型は宣言によってDの名前空間にコピーされます。
逆引用符の演算子を使用して明示的に名前空間が指定されていない型宣言を検出すると、Dコンパイラは指定された型名を使用して一致する型名を検出するために、アクティブな型の名前空間のセットを検索します。常にCの名前空間が最初に検索され、その後でDの名前空間が検索されます。CとDのどちらの名前空間でも型名が見つからない場合は、アクティブなカーネル・モジュールの型の名前空間がロード・アドレスの順に検索されます。ただし、ロード可能なモジュール間の順序付けプロパティは保証されません。別のカーネル・モジュールとの型名の競合を避けるために、ロード可能なカーネル・モジュールで定義された型にアクセスする場合は、スコープ演算子を使用してください。
Dコンパイラは、OSのソース・コードに関連付けられた型にアクセスするために、コアLinuxカーネル・モジュールに付随する圧縮形式のANSI Cデバッグ情報を使用します。これにより、対応するCのインクルード・ファイルにアクセスする必要がなくなります。このシンボリック・デバッグ情報は、システム上のすべてのカーネル・モジュールで使用可能なわけではありません。DTraceで使用することを目的とした圧縮形式のCのデバッグ情報が含まれないモジュールの名前空間内の型にアクセスしようとすると、Dコンパイラはエラーを報告します。
変数
Dには、複数の変数タイプ(スカラー変数、連想配列、スカラー配列および多次元スカラー配列)があります。変数は明示的に宣言することで作成できますが、多くの場合、最初の使用時に暗黙的に作成されます。変数は、名前の競合を回避して変数の存続期間を明示的に制御するために、節またはスレッドのスコープに制限できます。
- スカラー変数
-
スカラー変数を使用して、整数やポインタなど固定サイズのデータ・オブジェクトを個別に表現します。スカラー変数は、1つ以上のプリミティブ型や複合型で構成された固定サイズのオブジェクトに使用することもできます。Dには、オブジェクトの配列と複合構造体を作成する機能があります。DTraceでは、事前定義された最大長まで拡張することによって文字列を固定サイズのスカラーとして表現することもできます。
スカラー変数を作成するには、次の形式の代入式を記述します:
nameは任意の有効なD識別子です。expressionは変数に格納する任意の値または式です。name = expression ;
DTraceには、Dプログラム内で参照できる複数の組込みスカラー変数が含まれています。こうした変数の値は、DTraceによって自動的に移入されます。これらの変数の完全なリストは、「DTraceの組込み変数リファレンス」を参照してください。
- 連想配列
-
連想配列は、キーを指定することで取得できるデータ要素の集合を表現するために使用します。連想配列は、通常の固定サイズの配列とは異なり、要素数に事前定義された制限がなく、任意の式をキーとして使用できます。さらに、連想配列内の要素は、連続した格納場所には格納されません。
連想配列を作成するには、次の形式の代入式を作成します:
name [ key ] = expression ;
nameは任意の有効なD識別子です。keyは1つ以上の式(通常は文字列値)のカンマ区切りリストです。expressionは指定したキーの配列に格納される値です。
配列に格納される各オブジェクトの型も、配列内のすべての要素に対して固定されます。「型、演算子および式」で定義されている代入演算子を使用すると、演算子ごとに定義されたオペランド・ルールに従って、連想配列の要素を変更できます。互換性のない代入を実行しようとすると、Dコンパイラは該当するエラー・メッセージを返します。連想配列のキーや値には、スカラー変数で使用できる任意の型を使用できます。
連想配列の値は、配列名と適切なキーを指定することで参照します。
連想配列の要素を削除するには、その要素に0を代入します。配列内の要素を削除すると、その要素に使用されていた記憶域の割当てが解除され、システムで使用できるようになります。
- スカラー配列
-
スカラー配列は、それぞれに同じ型の値が格納される、連続する固定長のメモリーの場所のグループです。スカラー配列にアクセスするには、ゼロから始まる整数を使用して各場所を参照します。スカラー配列は、Dでは連想配列ほどは多用されません。
次のように、
int
型を使用して5つの整数によるDスカラー配列を宣言します。宣言には接尾辞として要素数を大カッコで囲んだものを付加します。int s[5];
D式
s[0]
は最初の配列要素を参照し、s[1]
は2番目の配列要素を参照しています(以下同様)。DTraceは、コンパイル時にスカラー配列の索引に対して境界チェックを実行して、不正な索引参照を早期に捕捉できるようにしています。ノート:
構文的には、スカラー配列と連想配列は類似しています。次のように、整数によって参照される整数の連想配列を宣言できます。
int a[int];
この配列は、式
a[0]
を使用して参照することもできますが、記憶域と実装の観点からすると2つの配列は異なったものです。スカラー配列のs
は、ゼロから番号付けされた連続する5つのメモリーの場所で構成され、索引は配列に割り当てられた記憶域でのオフセットを示します。ところが、連想配列a
には事前定義されたサイズがなく、要素は連続したメモリーの場所に格納されません。また、連想配列のキーは、対応する値の記憶域の場所とは関係ありません。連想配列の要素a[0]
とa[-5]
にアクセスすると、DTraceによって2ワードの記憶域のみが割り当てられます。さらに、これらの要素は連続している必要はありません。連想配列のキーは、対応する値の抽象名であり、値の記憶域の場所とは関係ありません。初期値割当てを使用して配列を作成し、単一の整数式を配列索引として使用する場合(たとえば、
a[0] = 2
)、この式ではa
をスカラー配列への代入として解釈することも可能ですが、Dコンパイラは常に新しい連想配列を生成します。この場合、Dコンパイラに配列のサイズの定義を認識させ、この配列がスカラー配列であると推断させるには、スカラー配列を事前に宣言する必要があります。 - 多次元スカラー配列
-
Dでは、多次元のスカラー配列はあまり使用されません。これは、ANSI Cとの互換性を実現するためや、この機能を使用してCで作成されたOSデータ構造を監視およびアクセスするために提供されています。多次元配列は、基本型の後ろに連続する一連のスカラー配列サイズを大カッコ
[]
で囲んだ形式で宣言します。たとえば、12行x34列の整数値で構成される固定サイズの2次元配列を宣言するには、次の宣言を記述します:int s[12][34];
多次元スカラー配列は、同様の表記法を使用してアクセスできます。たとえば、行
0
列1
に格納されている値にアクセスするには、次のようなD式を記述します。s[0][1]
多次元スカラー配列値の記憶域の場所を計算するには、行番号に、宣言された列数の合計を乗算してから、列番号を加算します。
多次元配列の構文と、連想配列アクセスのD構文を混同しないように注意してください(
s[0][1]
とs[0,1]
は同じでないということです)。連想配列に互換性のないキー式を使用した場合や、スカラー配列の連想配列アクセスを試行した場合は、Dコンパイラにより該当するエラー・メッセージが報告され、プログラムのコンパイルが拒否されます。
変数のスコープ
変数スコープは、プログラム内で変数名が有効になる場所を定義して、変数の名前の競合を回避するために使用されます。スコープ付き変数を使用すると、プログラム全体、特定のスレッドまたは特定の節に対して、変数インスタンスの可用性を制御できます。
次の表に、使用可能な3つの主要変数スコープをリストして説明します。外部変数は、Dプログラムの制御外にある4番目のスコープを提供する点に注意してください。
スコープ | 構文 | 初期値 | スレッド・セーフ対応 | 説明 |
---|---|---|---|---|
グローバル |
|
0 |
いいえ |
いずれかのスレッドで起動するプローブは、変数の同じインスタンスにアクセスします。 |
スレッド・ローカル |
|
0 |
はい |
スレッドで起動するプローブは、変数のスレッド固有のインスタンスにアクセスします。 |
節ローカル |
|
未定義 |
はい |
起動するプローブは、プローブのその特定の起動に固有の変数のインスタンスにアクセスします。 |
ノート:
次の点に注意してください。
-
スカラー変数と連想配列はグローバルなスコープのため、マルチプロセッサ・セーフ(MPセーフ)ではありません。このような変数の値は複数のプロセッサによる変更の可能性があるため、複数のプローブが変数を変更すると、変数が破損する可能性があります。
-
集積体は、グローバル・スコープであってもMPセーフです。これは、最終的な集積体でグローバル結果が生成される前に、独立したコピーがローカルで更新されるためです。
グローバル変数
グローバル変数は、Dプログラム全体にわたって永続する変数記憶域を宣言するために使用します。グローバル変数は、最も広いスコープを提供します。
Dプログラムでは、連想配列を含む任意の型のグローバル変数を定義できます。次に、グローバル変数の定義例を示します:
x = 123; /* integer value */
s = "hello"; /* string value */
a[123, 'a'] = 456; /* associative array */
グローバル変数は、最初の代入時に自動的に作成され、最初の代入文の右辺に適した型を使用します。スカラー配列を除き、グローバル変数は使用前に明示的に宣言する必要はありません。あえて宣言を作成する場合は、プログラム節の外側に宣言を配置する必要があります。次に例を示します:
int x; /* declare int x as a global variable */
int x[unsigned long long, char];
syscall::read:entry
{
x = 123;
a[123, 'a'] = 456;
}
D変数の宣言では初期値を代入することはできません。初期値の代入には、BEGIN
プローブ節を使用できます。すべてのグローバル変数の記憶域は、最初にこの変数を参照する前に、DTraceによってゼロが入力されます。
スレッド・ローカル変数
スレッド・ローカル変数は、各OSスレッドにローカルな変数記憶域を宣言するために使用します。スレッド・ローカル変数は、プローブを有効にし、プローブを起動するすべてのスレッドにタグや他のデータでマークする場合に便利です。
スレッド・ローカル変数を参照するには、次のように、特殊な識別子self
に->
演算子を適用します。
syscall::read:entry
{
self->read = 1;
}
このDコードの例では、read()
システム・コールでプローブを有効にし、このプローブを起動する各スレッドに、read
という名前のスレッド・ローカル変数を関連付けています。スレッド・ローカル変数は、グローバル変数と同様に、最初の代入時に自動的に作成され、最初の代入文の右辺で使用された型(この例ではint
)が適用されます。
Dプログラム内でself->read
変数が参照されるたびに、それに対応するDTraceプローブの起動時に実行されていたOSスレッドに関連付けられているデータ・オブジェクトが参照されます。スレッド・ローカル変数は、システム内でスレッドのIDを示すタプルによって暗黙的に索引が付けられた連想配列とみなすことができます。スレッドのIDは、システムの存続期間中は一意です。スレッドが終了し、同じOSのデータ構造を使用してスレッドが作成された場合、このスレッドに同じDTraceのスレッド・ローカル記憶域のIDが再利用されることはありません。
スレッド・ローカル変数を定義すると、その変数は以前に特定のスレッドに割り当てられていない場合でも、システム内の任意のスレッドで参照できます。スレッド・ローカル変数のスレッドのコピーがまだ割り当てられていない場合は、コピーのデータ記憶域はゼロで満たされるように定義されます。連想配列の要素と同様に、スレッド・ローカル変数の基礎となる記憶域は、この変数にゼロ以外の値が代入されるまでは割り当てられません。また、連想配列要素の場合と同じく、スレッド・ローカル変数にゼロを割り当てると、基礎となる記憶域の割当てが解除されます。使用していないスレッド・ローカル変数には、常にゼロを割り当ててください。
Dプログラムでは、連想配列を含む任意の型のスレッド・ローカル変数を定義できます。次に、スレッド・ローカル変数の定義例を示します。
self->x = 123; /* integer value */
self->s = "hello"; /* string value */
self->a[123, 'a'] = 456; /* associative array */
スレッド・ローカル変数は使用前に明示的に宣言する必要はありません。あえて宣言を作成する場合は、キーワードself
を先頭に付けてプログラム節の外側に配置します。次に例を示します:
self int x; /* declare int x as a thread-local variable */
syscall::read:entry
{
self->x = 123;
}
スレッド・ローカル変数はグローバル変数とは別の名前空間に格納されるため、変数名を再利用できます。プログラム内で名前をオーバーロードする場合、x
とself->x
は同じ変数にならないことに注意してください。
節ローカル変数
節ローカル変数は、変数の格納をプローブの特定の起動に制限するために使用します。節ローカルが最も狭いスコープです。プローブがCPUで起動すると、Dスクリプトがプログラム順に実行されます。各節ローカル変数は、スクリプトで初めて使用するときに未定義の値でインスタンス化されます。Dスクリプトがプローブの特定の起動の実行を完了するまで、変数の同じインスタンスがすべての節で使用されます。
this->
の接頭辞を付けて、節ローカル変数を参照して割り当てることができます。
BEGIN
{
this->secs = timestamp / 1000000000;
...
}
節ローカル変数を使用前に明示的に宣言するには、this
キーワードを使用します:
this int x; /* an integer clause-local variable */
this char c; /* a character clause-local variable */
BEGIN
{
this->x = 123;
this->c = 'D';
}
プログラムに1つのプローブに対する複数の節が含まれている場合、節が順番に実行され同じプローブを有効にしている異なる節にわたって永続するため、節ローカル変数はそのまま残ることに注意してください。節ローカル変数は、同じプローブを有効にしている複数の節にわたって永続しますが、指定されたプローブに対して処理される最初の節では、その値は未定義になります。予期しない結果を回避するには、それぞれの節ローカル変数に適切な値を代入してから使用します。
節ローカル変数は、任意のスカラー変数型を使用して定義できますが、連想配列は、節ローカル・スコープを使用して定義できません。節ローカル変数のスコープは、対応する変数データにのみ適用されます。この変数に定義された名前や型のアイデンティティには適用されません。節ローカル変数が定義されると、それ以降のすべてのDプログラム節で、この名前と型のシグネチャを使用できます。
節ローカル変数を使用して、計算の途中結果を累積できます。また、節ローカル変数を他の変数の一時コピーとして使用することもできます。節ローカル数には、連想配列よりも高速にアクセスできます。このため、同じDプログラム節内で連想配列値を複数回参照する必要がある場合は、その値を最初に節ローカル変数にコピーして、ローカル変数を繰り返し参照すると、効率がよくなります。
外部変数
D言語では、特別なスコープ演算子として逆引用符(`
)を使用することで、Dプログラム自体の外部にあるOSで定義されているシンボルや変数にアクセスします。
DTraceインストゥルメンテーションは、Oracle Linux OSカーネル内で実行されます。そのため、特別なDTrace変数およびプローブ引数にアクセスする以外に、カーネル・データ構造体、シンボルおよび型にもアクセスできます。DTraceのユーザー、管理者、サービス担当者およびドライバ開発者は、これらの機能を利用して、OSカーネルやデバイス・ドライバの低レベルの動作を調査できます。
たとえば、Oracle Linuxカーネルには、max_pfn
という名前のシステム変数のC宣言が含まれています。この変数は、次のように、カーネル・ソース・コード内にCで宣言されます。
unsigned long max_pfn
この変数の値をDプログラム内でトレースするには、次のようなD文を作成します。
trace(`max_pfn);
DTraceでは、カーネル・シンボルに、対応するOSのCコード内のシンボルに使用されている型が関連付けられます。このため、ローカルOSのデータ構造にソース・ベースでアクセスできます。
カーネル・シンボル名は、D変数および関数識別子とは別の名前空間に保持されます。そのため、これらの名前が別のD変数名が競合することを懸念する必要はありません。変数の直前に逆引用符を付けると、一致する変数定義を見つけるために、既知のカーネル・シンボルが検索され、ロードされたモジュールのリストが使用されます。Oracle Linuxカーネルでは、個別のシンボル名前空間のモジュールを動的にロードできるため、アクティブなOSカーネル内で同じ変数名を複数回使用されることがあります。こうした名前の競合を解消するには、アクセスする変数を含むカーネル・モジュール名をシンボル名の逆引用符より前に指定します。たとえば、次のようにして、foo
という名前のカーネル・モジュールによって提供される_bar
関数のアドレスを参照します:
foo`_bar
外部変数には、オペランド型の通常ルールに従い、値を変更するものを除いた任意のD演算子を適用できます。必要に応じて、Dコンパイラはアクティブなカーネル・モジュールに対応する変数名をロードするため、これらの変数を宣言する必要はありません。外部変数には、その値を変更するような演算子(=
や+=
など)は適用できません。安全上の理由により、DTraceは監視対象のソフトウェアの状態を破損できないようにしています。
Dプログラムから外部変数へアクセスすることは、OSカーネルやそのデバイス・ドライバなどの別のプログラムの内部実装の詳細にアクセスすることを意味します。そうした実装の詳細は、信頼できる安定したインタフェースを形成しません。そうした詳細に依存するDプログラムを作成すると、対応するソフトウェアの次回アップグレード時に動作しなくなる可能性があります。このため、外部変数は通常、DTraceを使用してパフォーマンスや機能上の問題をデバッグするために使用されます。
ポインタ
ポインタは、OS、ユーザー・プログラム、またはDスクリプトによって使用されるデータ・オブジェクトと参照メモリーのメモリー・アドレスです。Dのポインタはデータ・オブジェクトで、整数型の仮想アドレス値を格納し、対応するメモリーの場所に格納されているデータのフォーマットを説明するD型と関連付けます。
D変数をポインタ型として明示的に宣言するには、参照されるデータの型を最初に指定してから、型名にアスタリスク(*
)を付加します。これを行うと、次の文に示すように、ポインタ型を宣言することを示します。
int *p;
この文では、整数へのポインタであるp
というDグローバル変数を宣言しています。この宣言は、p
が64ビットの整数で、その値はメモリー内のどこかの場所にある別の整数のアドレスであることを意味します。コンパイルされた形式のDコードは、カーネル自体の内部でプローブの起動時に実行されます。通常、Dポインタはカーネルのアドレス空間に関連付けられたポインタです。
カーネル内のデータ・オブジェクトへのポインタを作成する場合は、&
演算子を使用してそのアドレスを計算できます。たとえば、カーネル・ソース・コードでunsigned long max_pfn
変数を宣言します。この変数のアドレスをトレースするには、Dでのオブジェクト名に&
演算子を付加した結果をトレースします。
trace(&`max_pfn);
*
演算子は、ポインタによってアドレス指定されたオブジェクトを指定するときに使用できます。これは、&
演算子と正反対に機能します。たとえば、次の2つのDのコード部分は、意味的に同じです。
q = &`max_pfn; trace(*q);
trace(`max_pfn);
この例では、最初のコード部分で、Dグローバル変数ポインタq
を作成しています。max_pfn
オブジェクトはunsigned long
型のため、&`max_pfn
の型はunsigned long
へのポインタであるunsigned long *
になります。q
の型は宣言に暗黙的に含まれます。*q
の値をトレースすると、ポインタがデータ・オブジェクトmax_pfn
に戻ります。したがって、このコード部分は、データ・オブジェクトの名前を使用してその値を直接トレースする2番目のコード部分と同じです。
ポインタの安全性
DTraceは、Dプログラムを実行するための堅牢で安全な環境です。バグのあるDプログラムを作成した場合でも、無効なDポインタ・アクセスが原因でDTraceやOSカーネルに障害やクラッシュが発生することはありません。それどころか、DTraceソフトウェアは無効なポインタ・アクセスを検出して、BADADDR
フォルトを返します。現在の節の実行は終了して、ERRORプローブを起動し、トレースはプログラムがERRORプローブに対してexit
をコールしないかぎり続行されます。
ポインタは、OSのCでの実装の本質的な部分であるためDには必須ですが、DTraceにはJavaプログラミング言語と同様の安全性メカニズムが実装されているため、バグのあるプログラムがそれ自体や別のプログラムに影響を与えることはありません。DTraceのエラー・レポート機能は、Javaプログラミング言語のランタイム環境と同じように、プログラミング・エラーを検出し、例外をレポートします。
DTraceのエラー処理およびレポート機能について観察するために、ポインタを使用して意図的に不正なDプログラムを作成します。たとえば、エディタで次のDプログラムを入力し、badptr.d
という名前のファイルに保存します。
BEGIN
{
x = (int *)NULL;
y = *x;
trace(y);
}
badptr.d
プログラムでは、キャスト式を使用してNULL
を整数のポインタに変換しています。このポインタは式*x
を使用して間接参照され、その結果は別の変数y
に代入されています。その後、y
のトレースが試行されています。このDプログラムを実行すると、DTraceはy = *x
という文が処理される時点で無効なポインタ・アクセスを検出して、エラーを報告します。
dtrace: script '/tmp/badptr.d' matched 1 probe
dtrace: error on enabled probe ID 2 (ID 1: dtrace:::BEGIN): invalid address (0x0) in action #1 at BPF pc 156
Dプログラムはエラーを無視して実行を続行することに注目してください。システムと監視されたすべてのプロセスは影響を受けません。スクリプトにERROR
プローブを追加して、Dエラーを処理することもできます。DTraceのエラー・メカニズムの詳細は、「ERRORプローブ」を参照してください。
ポインタと配列の関係
スカラー配列は、最初の記憶域の場所を示すアドレスに関連付けられた変数で表されます。ポインタは、定義された型の記憶域の場所のアドレスでもあります。したがって、Dでは、ポインタ変数と配列変数の両方で、配列索引の[]
表記を使用できます。たとえば、次の2つのDのコード部分は、意味的に同じです。
p = &a[0]; trace(p[2]);
trace(a[2]);
最初のコード部分では、&
演算子を式a[0]
を適用することにより、ポインタp
をスカラー配列a
内の最初の要素のアドレスに割り当てています。式p[2]
では、3番目の配列要素(索引2)の値がトレースされます。p
には、a
に関連付けられたものと同じアドレスが含まれるため、この式では、2番目のコード部分に示されるa[2]
と同じ値が導き出されます。この等価性の結果として、Dでは任意のポインタまたは配列の任意の索引へのアクセスが可能になります。スカラー配列に事前定義されたサイズを超えたメモリーにアクセスすると、予測不能な結果を得るか、DTraceが無効なアドレスのエラーを報告します。
ポインタと配列の違いは、ポインタ変数は、別の記憶域の整数アドレスを格納している個別の記憶域を参照するのに対して、配列変数は、配列の場所を格納している整数の場所ではなく、配列記憶域自体を指定することです。
この違いは、ポインタとスカラー配列を割り当てるときのD構文から明らかになります。x
とy
がポインタ変数の場合、式x = y
は正当です。この式では、y
内のポインタ・アドレスが、x
で指定された記憶域の場所にコピーされます。x
とy
がスカラー配列変数の場合、式x = y
は不正です。Dでは、全体としての配列の代入はできません。p
がポインタで、a
がスカラー配列である場合、文p = a
を使用できます。この文は、文p = &a[0]
と同等です。
ポインタ演算
Cと同様に、Dのポインタ演算は整数の演算と同じではありません。ポインタ演算では、ポインタで参照されている型のサイズとオペランドの乗除によって、基礎となるアドレスが暗黙的に変更されます。
次のDのコード部分は、この特性を示しています。
int *x;
BEGIN
{
trace(x);
trace(x + 1);
trace(x + 2);
}
このコード部分では、整数ポインタx
が作成され、その値、その値を1増分した値、およびその値を2増分した値がトレースされています。このプログラムを作成して実行すると、整数値0
、4
および8
が報告されます。
x
はint
(サイズは4バイト)のポインタのため、x
を増分すると、基礎となるポインタ値に4が加算されます。この特性は、ポインタを使用して配列などの連続した記憶域の場所を参照する場合に役立ちます。たとえば、配列a
のアドレスにx
が割り当てられていた場合、式x + 1
は式&a[1]
と同等になります。同様に、式*(x + 1)
は、値a[1]
を参照することになります。+
、++
または=+
演算子を使用してポインタ値が増分される場合は常に、ポインタ演算が実装されます。ポインタ演算は、左側のポインタから整数が減算される場合、ポインタから別のポインタが減算される場合、ポインタに--
演算子が適用される場合も適用されます。
たとえば、次のDプログラムでは、結果として2
がトレースされます。
int *x, *y;
int a[5];
BEGIN
{
x = &a[0];
y = &a[2];
trace(y - x);
}
汎用ポインタ
Dプログラムでは、ポインタによって参照されるデータの型を指定することなく、汎用ポインタ・アドレスを表現または操作すると便利な場合があります。汎用ポインタは、void *
型を使用して指定できます。キーワードvoid
は特定の型情報が存在しないことを示します。また、組込み型の別名uintptr_t
を使用して汎用ポインタを指定することもできます。これは、現在のデータ・モデル内のポインタに適したサイズの符号なし整数型の別名です。void *
型のオブジェクトにはポインタ演算を適用できません。こうしたポインタを間接参照するには、最初に別の型にキャストする必要があります。ポインタ値に対して整数演算を実行する必要がある場合は、ポインタをuintptr_t
型にキャストします。
void
へのポインタは、連想配列のタプルの式や代入文の右側など、別のデータ型へのポインタが必要な場合はいつでも使用できます。同様に、void
へのポインタが必要な場合は、任意のデータ型へのポインタを使用できます。void
以外の型へのポインタを、void
以外の別のポインタ型のかわりに使用するには、明示的なキャストが必要です。ポインタをuintptr_t
などの整数型に変換したり、これらの整数を適切なポインタ型に変換し戻す場合は常に、明示的キャストを使用する必要があります。
DTraceオブジェクトへのポインタ
Dコンパイラでは、連想配列、組込み関数および変数などのDTraceオブジェクトへのポインタを、&
演算子を使用して取得することは禁じられています。DTrace実行環境では、次のプローブが起動するまでの間に必要に応じて変数のアドレスを自由に再配置できるように、それらの変数のアドレスを取得することが禁じられています。これにより、DTraceはプログラムに必要なメモリーをより効率的に管理できます。複合構造体を作成する場合は、DTraceオブジェクト記憶域のカーネル・アドレスを取得する式を作成できます。Dプログラムでは、このような式は作成しないでください。このような式の使用が必要な場合は、プローブを起動するたびにアドレスが同じであると想定しないでください。
ポインタおよびアドレス空間
ポインタは、仮想アドレス空間内で物理メモリーへの変換を提供するアドレスです。DTraceでは、DプログラムがOSカーネル自体のアドレス空間内で実行されます。Linuxシステムでは、多数のアドレス空間が管理されます。これには、OSカーネル用のアドレス空間と、各ユーザー・プロセス用のアドレス空間があります。各アドレス空間は、システム上のすべてのメモリーにアクセスできるように見えるため、複数のアドレス空間で同じ仮想アドレス・ポインタ値を再使用できますが、異なる物理メモリーに変換されます。したがって、ポインタを使用するDプログラムを作成する場合、使用するポインタに対応するアドレス空間を意識する必要があります。
たとえば、syscall
プロバイダを使用して、引数として整数または整数配列へのポインタを取るシステム・コール(pipe()
など)のエントリをインストゥルメントする場合、*
または[]
演算子を使用してそのポインタまたは配列を間接参照することはできません。これは、そのアドレスが、システム・コールを実行したユーザー・プロセスのアドレス空間内のアドレスであるためです。Dでは、このアドレスに*
または[]
演算子を適用すると、カーネル・アドレス空間がアクセスされ、無効なアドレスによるエラーが発生します。または、このアドレスが有効なカーネル・アドレスと偶然同じであるかどうかに応じて、Dプログラムに予測不能なデータが返されます。
DTraceプローブからユーザー・プロセス・メモリーにアクセスするには、関数copyin
、copyinstr
またはcopyinto
のいずれかを適用する必要があります。混乱を避けるために、Dプログラムの作成時には、ユーザー・アドレスを格納する変数に適切な名前とコメントを付けるようにしてください。ユーザー・アドレスを間接参照するようなDコードを誤ってコンパイルすることがないように、ユーザー・アドレスはuintptr_t
として格納することもできます。
構造体および共用体
関連する変数の集合は、構造体および共用体と呼ばれる複合データ・オブジェクトにグループ化できます。Dでこれらのオブジェクトを定義するには、これらに対して新しい型定義を作成します。新しい型は、連想配列の値を含む任意のD変数に使用できます。この項では、このような複合型を作成し操作するための構文とセマンティクスを紹介し、これらと相互に作用するD演算子について説明します。
構造体
Dのキーワードstruct
(structure (構造体)の略)は、他の型のグループから構成される新しい型を導入するために使用します。この新しいstruct
型をDの変数や配列の型として使用することにより、関連する変数のグループを単一の名前で定義できます。Dの構造体は、CやC++の対応する構造体と同じです。Javaプログラミング言語でプログラミングした経験がある方であれば、Dの構造体を、メソッドを持たずデータ・メンバーのみが含まれるクラスのようなものと考えてください。
アプリケーションに対して実行されるread()
およびwrite()
システム・コールに関するいくつかの事項(経過時間、コール回数、引数として渡された最大バイト数など)を記録する、より洗練されたシステム・コールのトレース・プログラムをDで作成するとします。
次の例に示すように、これらのプロパティを4つの個別の連想配列に記録するD節を作成します:
int ts[string]; /* declare ts */
int calls[string]; /* declare calls */
int elapsed [string]; /* declare elapsed */
int maxbytes[string]; /* declare maxbytes */
syscall::read:entry, syscall::write:entry
/pid == $target/
{
ts[probefunc] = timestamp;
calls[probefunc]++;
maxbytes[probefunc] = arg2 > maxbytes[probefunc] ?
arg2 : maxbytes[probefunc];
}
syscall::read:return, syscall::write:return
/ts[probefunc] != 0 && pid == $target/
{
elapsed[probefunc] += timestamp - ts[probefunc];
}
END
{
printf(" calls max bytes elapsed nsecs\n");
printf("------ ----- --------- -------------\n");
printf(" read %5d %9d %d\n",
calls["read"], maxbytes["read"], elapsed["read"]);
printf(" write %5d %9d %d\n",
calls["write"], maxbytes["write"], elapsed["write"]);
}
構造体を使用すると、プログラムが読みやすくなり、保守が簡単になります。構造体は、ひとまとめにするデータ項目の論理的なグループ化を実現します。また、すべてのデータ項目を単一のキーで格納できるため、記憶域空間の節約にもなります。
まず、Dプログラムのソース・ファイルの冒頭で新しいstruct
型を宣言します。
struct callinfo {
uint64_t ts; /* timestamp of last syscall entry */
uint64_t elapsed; /* total elapsed time in nanoseconds */
uint64_t calls; /* number of calls made */
size_t maxbytes; /* maximum byte count argument */
};
struct
キーワードの後ろには、新しい型を参照するために使用するオプションの識別子を続けます。この型はstruct callinfo
と呼ぶことにします。次に、構造体のメンバーを中カッコ{}
で囲み、宣言全体はセミコロン(;
)で終わらせます。各構造体メンバーは、D変数宣言と同じ構文を使用して定義されます。最初にメンバーの型、次にメンバーを指定する識別子名、最後に別のセミコロン(;
)が並びます。
struct
宣言では新しい型を定義します。DTraceでは、これにより変数を作成することや、記憶域を割り当てることはありません。宣言すると、この後のDプログラム全体でstruct callinfo
を型として使用できます。struct callinfo
型の各変数には、構造テンプレートで記述されている4つの変数のコピーが格納されます。メンバーは、メンバー・リストの順序に従ってメモリー内に配置されます。データ・オブジェクトの配置に必要な場合は、メンバー間にパディング・スペースが挿入されます。
個々のメンバー値にアクセスするには、.
演算子とともにメンバーの識別子名を使用して、次のような形式の式を作成します。
variable-name.member-name
次の例は、新しい構造体型を使用した改良版のプログラムを示します。テキスト・エディタで、次のDプログラムを入力し、rwinfo.d
という名前のファイルに保存します。
struct callinfo {
uint64_t ts; /* timestamp of last syscall entry */
uint64_t elapsed; /* total elapsed time in nanoseconds */
uint64_t calls; /* number of calls made */
size_t maxbytes; /* maximum byte count argument */
};
struct callinfo i[string]; /* declare i as an associative array */
syscall::read:entry, syscall::write:entry
/pid == $target/
{
i[probefunc].ts = timestamp;
i[probefunc].calls++;
i[probefunc].maxbytes = arg2 > i[probefunc].maxbytes ?
arg2 : i[probefunc].maxbytes;
}
syscall::read:return, syscall::write:return
/i[probefunc].ts != 0 && pid == $target/
{
i[probefunc].elapsed += timestamp - i[probefunc].ts;
}
END
{
printf(" calls max bytes elapsed nsecs\n");
printf("------ ----- --------- -------------\n");
printf(" read %5d %9d %d\n",
i["read"].calls, i["read"].maxbytes, i["read"].elapsed);
printf(" write %5d %9d %d\n",
i["write"].calls, i["write"].maxbytes, i["write"].elapsed);
}
このプログラムを実行して、コマンドの結果を返します。たとえば、dtrace -q -s rwinfo.d -c /bin/dateコマンドを実行します。dateプログラムが実行され、終了して結果を出力するEND
プローブが起動するまでトレースされます。
# dtrace -q -s rwinfo.d -c date
...
calls max bytes elapsed nsecs
------ ----- --------- -------------
read 2 4096 10689
write 1 29 9817
構造体へのポインタ
CやDでは、一般にポインタを使用して構造体を参照します。演算子->
を使用すると、ポインタを使用して構造体のメンバーにアクセスできます。構造体struct s
がメンバーm
を持つときに、この構造体にsp
という名前のポインタがある(ここで、sp
はstruct s *
型の変数)場合、メンバーにアクセスする方法は2通りあります。1つは、演算子*
を使用して最初にポインタsp
を間接参照する方法です。
struct s *sp;
(*sp).m
また、同じことをするために、->
演算子も使用できます:
struct s *sp;
sp->m
DTraceでは、構造体へのポインタとしていくつかの組込み変数が用意されています。たとえば、ポインタcurpsinfo
は、struct
psinfo
を参照します。その内容は、現在のプローブを起動したスレッドに関連付けられているプロセスの状態に関する情報のスナップショットを提供します。次の表に、curpsinfo
を使用する式の例とその型および意味をリストします。
式の例 | 型 | 内容 |
---|---|---|
|
|
現在のプロセスID |
|
|
実行可能ファイル名 |
|
|
最初のコマンドライン引数 |
次の例では、pr_fname
メンバーを使用して、特定のプロセスを識別しています。エディタで次のスクリプトを入力し、procfs.d
という名前のファイルに保存します。
syscall::write:entry
/ curpsinfo->pr_fname == "date" /
{
printf("%s run by UID %d\n", curpsinfo->pr_psargs, curpsinfo->pr_uid);
}
この節では、curpsinfo->pr_fname
という式を使用して、コマンド名にアクセスし、これを照合しています。これにより、引数をトレースする前に正しいwrite()
リクエストを選択できるようになります。ここでは、==
演算子を使用し、左側にはchar
配列の引数、右側には文字列の引数を配置しています。これにより、Dコンパイラは、左側の引数を文字列に昇格できると判断して、文字列比較が実行されます。一方のシェルでコマンドdtrace -q -s procs.dを入力してから、もう一方のシェルでdateコマンドのいくつかのバリエーションを実行します。DTraceによって表示される出力は、次のようになります。これは、curpsinfo->pr_psargs
がコマンドの起動方法とコマンドに含まれる引数を表示できることを示しています:
# dtrace -q -s procfs.d
date run by UID 500
/bin/date run by UID 500
date -R run by UID 500
...
^C
#
Cプログラムでは複雑なデータ構造体がよく使用されます。このため、Dから構造体を記述および参照する機能には、Oracle Linux OSカーネルとそのシステム・インタフェースの内部動作を監視するための強力な機能も用意されています。
共用体
共用体は、ANSI CとDで使用できる別の種類の複合型で、構造体に関連しています。共用体は複合型であり、ここでは、異なる型の一連のメンバーが定義され、メンバー・オブジェクトはすべて同じ記憶域のリージョンを占有します。そのため、共用体はバリアント型のオブジェクトといえます。つまり、任意の時点で有効なメンバーは1つのみであり、このメンバーは共用体の割当て方法によって決まります。通常、別の変数や状態の一部を使用して、共用体のどのメンバーが現在有効であるかを示します。共用体のサイズは、その最大のメンバーのサイズです。共用体に使用されるメモリー・アライメントは、共用体のメンバーに必要な最大アライメントになります。
メンバー・サイズおよびオフセット
sizeof
演算子を使用すると、struct
またはunion
を含むあらゆるD型または式のサイズ(バイト単位)を特定できます。sizeof
演算子は、次の2つの例のように、式、または丸カッコで囲んだ型名に適用できます。
sizeof expression
sizeof (type-name)
たとえば、式sizeof (uint64_t)
は値8
を返します。また、式sizeof (callinfo.ts)
も、前述のプログラム例のソース・コードに挿入した場合、8
を返します。sizeof
演算子の正式な戻り型は、型の別名size_t
です。これは、現在のデータ・モデル内のポインタと同じサイズの符号なし整数として定義されていて、バイト数を表現するために使用されます。式にsizeof
演算子を適用すると、この式はDコンパイラによって評価されますが、結果のオブジェクト・サイズはコンパイル時に計算され、式のコードは生成されません。sizeof
は、整数定数が必要な任意の場所で使用できます。
比較演算子offsetof
を使用すると、struct
型またはunion
型のオブジェクトに関連付けられた記憶域の開始位置から、構造体または共用体メンバーのオフセットをバイト単位で判断できます。offsetof
演算子は、次の形式の式で使用します。
offsetof (type-name, member-name)
この場合、type-nameは、任意のstruct
またはunion
型の名前、または型の別名です。member-nameは、構造体または共用体のメンバーの名前を表す識別子です。sizeof
と同様に、offsetof
は、size_t
を返し、整数定数を使用できるDプログラム内の任意の場所で使用できます。
ビットフィールド
Dでは、ビットフィールドと呼ばれる、任意のビット数の整数の構造体または共用体メンバーを定義することもできます。ビットフィールドを宣言するには、次の例に示すように、符号付きまたは符号なしの整数基本型、メンバー名、およびフィールドに割り当てるビット数を示す接尾辞を指定します。
struct s
{
int a : 1;
int b : 3;
int c : 12;
};
ビットフィールド幅は、メンバー名の後ろのコロンに続く整数定数です。ビットフィールド幅は正の数であるとともに、対応する整数基本型の幅を超えないビット数である必要があります。Dでは、64ビットを超えるビットフィールドは宣言できません。Dのビットフィールドは、対応するANSI Cの機能と互換性があり、この機能にアクセスできます。通常、ビットフィールドは、メモリー記憶域が不足している場合や、構造体レイアウトがハードウェア・レジスタ・レイアウトと一致する必要がある場合に使用します。
ビットフィールドは、整数やマスク・セットのレイアウトを自動化し、メンバー値を抽出するコンパイラ構成です。自分でマスクを定義して、&
演算子を使用しても、同じ結果が得られます。CとDのコンパイラは、できるだけ効率的にビットをパッキングしようとしますが、その順序や方法は自由です。そのため、コンパイラやアーキテクチャが異なると、それらの間で同一のビット・レイアウトが生成される保証はありません。安定したビット・レイアウトが必要な場合は、自分でビット・マスクを作成して、&
演算子を使用して値を抽出してください。
ビットフィールド・メンバーにアクセスするには、その他の構造体や共用体のメンバーの場合と同様に、その名前に.
または->
演算子を指定します。ビットフィールドは、任意の式で使用できるように、次に大きい整数型に自動的に昇格されます。ビットフィールド記憶域はバイト境界上には配置できません。また、そのサイズをバイト数の概数にすることもできません。そのため、ビットフィールド・メンバーにsizeof
またはoffsetof
演算子は適用できません。Dコンパイラでは、&
演算子を使用してビットフィールド・メンバーのアドレスを指定することも禁じられています。
DTraceの文字列処理
DTraceには、文字列をトレースおよび操作する機能があります。この項では、文字列を宣言および操作するためのD言語機能の完全セットについて説明します。ANSI Cとは異なり、Dの文字列では、独自の組込み型と演算子がサポートされています。このため、これらはトレース・プログラム内で簡単かつ明確に使用できます。
文字列表現
DTraceでは、文字列はNULLバイト(値がゼロのバイトで、一般に'\0'
と記述されます)で終わる文字配列として表現されます。文字列の可視部分は可変長で、NULLバイトの場所によって長さが決まります。しかし、DTraceでは、各プローブで一定量のデータがトレースされるように、各文字列が固定サイズの配列に格納されます。文字列の長さは、事前に定義された文字列制限を超えることはできません。ただし、この制限はDプログラム内で変更できます。また、dtraceコマンドラインでstrsize
オプションをチューニングして変更することもできます。デフォルトの文字列制限は256バイトです。
D言語では、文字列を参照するために、char *
型を使用するかわりに、明示的なstring
型が用意されています。string型は、文字シーケンスのアドレスであるという点ではchar *
型と同等です。ただし、Dコンパイラやtrace
などのD関数は、string型の式に適用するときには拡張機能を使用できます。たとえば、string型では、文字列の実バイトをトレースする必要があるときに、char *
型のような曖昧さがありません。
次のD文で、s
がchar *
型である場合、ポインタs
の値がトレースされます(つまり、整数アドレス値がトレースされます)。
trace(s);
次のD文では、*
演算子の定義により、ポインタs
が間接参照され、その場所にある単一の文字がトレースされます。
trace(*s);
このような動作を利用すると、単一の文字や、文字列でないNULLバイトで終わらないバイト・サイズの整数配列を参照する文字ポインタを操作できます。
次のD文では、s
がstring
型である場合、このstring型からDコンパイラに対して、アドレスが変数s
に格納されているNULLで終わる文字列をトレースするよう指示されます。
trace(s);
文字列型の式の字句の比較を実行することもできます。「文字列比較」を参照してください。
文字列定数
文字列定数は、二重引用符のペア(""
)で囲まれ、自動的にstring
型が割り当てられます。文字列定数は任意の長さで定義できます。DTraceがシステムで消費できるメモリーの量と、DTraceランタイム・オプションのstrsize
に設定した制限によってのみ制限されます。文字列定数を宣言すると、Dコンパイラによって自動的に終了NULLバイト(\0
)が付加されます。文字列定数オブジェクトのサイズは、この文字列に関連付けられたバイト数に1バイト(終了NULLバイト用)を加えた長さです。
文字列定数には、リテラルの改行文字を含めることはできません。改行が含まれる文字列を作成するには、リテラルの改行のかわりに、\n
エスケープ・シーケンスを使用します。文字列定数には、文字定数に定義された特殊文字のエスケープ・シーケンスを含めることもできます。
文字列代入
char *
変数の代入の場合とは異なり、文字列は、参照ではなく値ごとにコピーされます。文字列代入演算子=
により、元のオペランドの文字列の実バイトとNULLバイトが、左側の変数(これはstring
型である必要があります)にコピーされます。
宣言を使用して文字列変数を作成できます:
string s;
また、string
型の式を代入して文字列変数を作成することもできます。
たとえば、次のようなD文があるとします。
s = "hello";
string
型の変数s
を作成して、そこに6バイトの文字列"hello"
(出力可能文字5バイトとNULLバイト)をコピーしています。
文字列代入は、Cライブラリ関数strcpy()
と類似していますが、元の文字列がコピー先の文字列の記憶域制限を超えた場合、最終的な文字列はこの制限上のNULLバイトで自動的に切り捨てられる点が異なります。
文字列変数には、文字列との互換性のある型の式を代入することもできます。この場合、元の式が自動的にstring型に昇格され、文字列代入が行われます。Dコンパイラでは、char *
型またはchar[n]
型(任意のサイズのchar
型のスカラー配列)の任意の式をstring型に昇格できます。
文字列変換
他の型の式は、キャスト式を使用したり、特殊なstringof
演算子を適用することにより、string
型に明示的に変換できます。どちらの場合も次の意味で同等です。
s = (string) expression;
s = stringof (expression);
この式は、文字列へのアドレスとして解釈されます。
stringof
演算子は、右側のオペランドに非常に緊密にバインドされます。オプションで、わかりやすくするために式はカッコを使用して囲むことができます。
ポインタや整数などのスカラー型の式、またはスカラー配列のアドレスは、スカラーが文字型へのアドレスとして解釈されることから文字列に変換できます。void
などの他の型の式は、string
には変換できません。無効なアドレスを誤って文字列に変換した場合でも、DTraceの安全機能により、システムやDTraceが破損することはありません。ただし、解読不能な文字シーケンスをトレースする可能性があります。
文字列比較
Dでは、バイナリ関係演算子が多重定義されており、これらを使用して、整数の比較のみでなく文字列の比較を行うこともできます。関係演算子では、両方のオペランドがstring
型であるか、一方のオペランドがstring
型でもう一方のオペランドがstring
に昇格可能である場合は常に、文字列比較が行われます。詳細は、「文字列代入」を参照してください。文字列の比較に使用できる関係演算子がリストされた表3-13も参照してください。
表3-13 文字列用のDの関係演算子
演算子 | 説明 |
---|---|
|
左のオペランドは右のオペランドより小さい |
|
左のオペランドは右のオペランド以下 |
|
左のオペランドは右のオペランドより大きい |
|
左のオペランドは右のオペランド以上 |
|
左のオペランドは右のオペランドと等しい |
|
左のオペランドは右のオペランドと等しくない |
整数の場合と同じく、各演算子は、int
型の値(条件が真の場合は1、偽の場合は0)に評価されます。
関係演算子では、Cライブラリ・ルーチンstrcmp()
の場合と同様に、2つの入力文字列がバイト単位で比較されます。各バイトは、ASCII文字セット内の対応する整数値を使用して比較されます。NULLバイトが読み取られるか最大文字列長に達したら、比較は終了します。詳細は、ascii(7)
マニュアル・ページを参照してください。Dの文字列比較とその結果の例を次の表に示します。
Dの文字列比較 | 結果 |
---|---|
|
1 (true)を返す |
|
1 (true)を返す |
|
0 (false)を返す |
ノート:
どちらかが正規化されていない場合は、同一のUnicode文字列が異なる文字列として判断される可能性があります。
集積体
集積体を使用すると、統計分析用のデータを蓄積できます。集積体は実行時に計算されるため、後処理は不要になり、処理が非常に効率的で正確になります。集積体は連想配列と同様に機能しますが、集積関数によって移入されます。Dでは、集積体の構文は次のとおりです。
@name[ keys ] = aggfunc( args );
集積体のnameは、特殊文字@
で始まるD識別子です。Dプログラムでは、名前が付けられたすべての集積体はグローバル変数です。集積体にスレッド・ローカルまたは節ローカルのスコープを設定することはできません。集積体名は、その他のDグローバル変数とは別の識別子名前空間に保持されます。名前を再利用する場合、a
と@a
は同じ変数ではないことに注意してください。特殊な集積体名@
は、Dプログラム内で匿名の集積体を指定する場合に使用できます。Dコンパイラは、この名前を集積体名@_
の別名として扱います。
集積体には、通常のものと索引付きのものがあります。索引付き集積体はキーを使用します。キーは、連想配列に使用される式のタプルと同様に、D式のカンマ区切りリストです。通常の集積体は、索引付き集積体と同様に扱われますが、索引付けのためにキーを使用しません。
aggfuncはDTrace集積関数の1つであり、argsはその関数に適した引数のカンマ区切りリストです。ほとんどの集積関数は、新規のデータを表す単一の引数を受け入れます。
集積関数
次の関数は、データを収集して意味のある方法で表示するために、プログラムで使用できる集積関数です。
-
avg
: 指定した式の算術平均を集積体に格納します。 -
count
: 増分されたカウント値を集積体に格納します。 -
max
: 指定した式のうちの最大値を集積体に格納します。 -
min
: 指定した式のうちの最小値を集積体に格納します。 -
sum
: 指定した式の合計値を集積体に格納します。 -
stddev
: 指定した式の標準偏差を集積体に格納します。 -
quantize
: 指定した式の値の二乗度数分布を集積体に格納します。オプションの増分を指定できます。 -
lquantize
: 指定した範囲のサイズで、指定した式の値の線形度数分布を集積体に格納します。 -
llquantize
: 対数線形度数分布を集積体に格納します。
集積体の出力
デフォルトでは、Dプログラムで導入された順番で複数の集積体が表示されます。この動作は、集積体を出力するprinta
関数を使用してオーバーライドできます。printa
関数では、フォーマット文字列を使用して集積データを正確にフォーマットすることもできます。
Dプログラム内の集積体がprinta
文でフォーマットされていないときに、dtraceコマンドを実行すると、集積体データのスナップショットが生成され、その結果はトレースの完了後にデフォルトの集積体の書式で出力されます。集積体がprinta
文でフォーマットされている場合、デフォルトの動作は無効になります。printa(@aggregation-name)
文をプログラムのEND
プローブ節に追加しても、同じ結果が得られます。
集積関数avg
、count
、min
、max
、stddev
およびsum
のデフォルトの出力フォーマットでは、それぞれのタプルの集積値に対応する10進整数値が表示されます。集積関数quantize
、lquantize
およびllquantize
のデフォルトの出力フォーマットでは、結果を含むASCIIヒストグラムが表示されます。集積体タプルの出力は、それぞれのタプル要素にtrace
が適用されたかのように出力されます。
データの正規化
ある程度の期間にわたってデータを集積するときには、なんらかの定数係数に基づいてデータを正規化できます。この技法を使用すると、ばらばらのデータをより簡単に比較できます。たとえば、システム・コールを集積する場合、システム・コールを実行の過程の絶対値ではなく、秒当たりのレートとして出力します。DTraceのnormalize
関数では、この方法でデータを正規化できます。normalize
のパラメータは、集積体と正規化の係数になります。集積体の結果としては、個々の値を正規化係数で割った値が出力されます。
投機
DTraceには、1つ以上のプローブの場所で暫定的にデータをトレースするために使用できる投機トレース機能が含まれています。その後、別のプローブの場所にある主バッファにデータをコミットできます。投機は、対象とする出力のみが含まれたデータをトレースするために使用できます。追加の処理は不要で、DTraceのオーバーヘッドが最小化になります。
- 一時的な投機バッファを設定します
- 投機バッファにトレースするように1つ以上の節に指示します
- 投機バッファ内のデータを主バッファにコミットするか、投機バッファを破棄します。
節内で適切な関数を使用することで、特定の条件が満たされたときに投機データをコミットするか破棄するかを選択できます。投機を使用すると、条件が満たされるまで一連のプローブのデータをトレースして、データが有用でない場合は廃棄することも、そうでない場合は保持することもできます。
次の表では、DTraceの投機関数について説明します。
表3-14 DTraceの投機関数
関数 | 引数 | 説明 |
---|---|---|
なし |
新しい投機バッファの識別子を返します |
|
ID |
これ以降の節の部分については、指定されたIDの投機バッファにトレースすることを示します。 |
|
ID |
IDに関連付けられている投機バッファをコミットします。 |
|
ID |
IDに関連付けられている投機バッファを破棄します。 |
例3-1 投機の使用方法
次の例は、投機の使用方法を示しています。投機の正しい動作には、すべての投機関数が一緒に使用されている必要があります。
投機はsyscall::open:entry
プローブに対して作成して、投機のIDはスレッド・ローカル変数にアタッチします。open()
システム・コールの最初の引数は、printf
関数を使用して投機バッファにトレースされます。
syscall::open:return
プローブには、さらに3つの節が含まれています。それらの節の最初で、投機バッファにerrno
をトレースします。2番目の節の述語では、ゼロ以外のerrno
値をフィルタ処理して、投機バッファをコミットします。3番目の節の述語では、ゼロのerrno
値をフィルタ処理して、投機バッファを破棄します。
プログラムの出力は主データ・バッファに返されるため、open()
システム・コールが失敗すると、プログラムは実質的にファイル名とエラー番号を返します。このコールが失敗しない場合は、投機バッファにトレースされた情報が破棄されます。
syscall::open:entry
{
/*
* The call to speculation() creates a new speculation. If this fails,
* dtrace will generate an error message indicating the reason for
* the failed speculation(), but subsequent speculative tracing will be
* silently discarded.
*/
self->spec = speculation();
speculate(self->spec);
/*
* Because this printf() follows the speculate(), it is being
* speculatively traced; it will only appear in the primary data buffer if the
* speculation is subsequently committed.
*/
printf("%s", copyinstr(arg0));
}
syscall::open:return
/self->spec/
{
/*
* Trace the errno value into the speculation buffer.
*/
speculate(self->spec);
trace(errno);
}
syscall::open:return
/self->spec && errno != 0/
{
/*
* If errno is non-zero, commit the speculation.
*/
commit(self->spec);
self->spec = 0;
}
syscall::open:return
/self->spec && errno == 0/
{
/*
* If errno is not set, discard the speculation.
*/
discard(self->spec);
self->spec = 0;
}