ポインタ
ポインタは、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]に適用することによって、スカラー配列a内の最初の要素のアドレスにポインタpが割り当てられています。 式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コンパイラによって実装されます。 ポインタ演算も次のように適用されます。左側のポインタから整数が減算される場合、ポインタから別のポインタが減算される場合、または--演算子がポインタに適用される場合です。
たとえば、次の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として格納することもできます。