構造体および共用体
関連する変数の集合は、構造体および共用体と呼ばれる複合データ・オブジェクトにグループ化できます。 これらのオブジェクトは、新しい型定義を作成して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);
}
このプログラムを実行して、コマンドの結果を返します。 たとえば、sudo dtrace -q -s rwinfo.d -c /bin/dateコマンドを実行します。
sudo dtrace -q -s rwinfo.d -c date
dateプログラムが実行され、終了するまでトレースされ、結果が出力されるENDプローブが起動されます:
...
calls max bytes elapsed nsecs
------ ----- --------- -------------
read 2 4096 10689
write 1 29 9817
構造体へのポインタ
CやDでは、一般にポインタを使用して構造体を参照します。 演算子->を使用すると、ポインタを使用して構造体のメンバーにアクセスできます。 struct sにメンバーmがあり、この構造体へのポインタspがある場合(spはstruct s *型の変数)、*演算子を使用して、最初に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コマンドのいくつかのバリエーションを実行します。
sudo dtrace -q -s procfs.d
DTraceによって表示される出力は、次のようになります。これは、curpsinfo->pr_psargsがコマンドの起動方法とコマンドに含まれる引数を表示できることを示しています:
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演算子を使用して、任意のD型または式(structまたはunionを含む)のサイズをバイト単位で指定できます。 次の2つの例に示すように、sizeof演算子は、式またはカッコで囲まれた型の名前に適用できます:
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コンパイラでは、&演算子を使用してビットフィールド・メンバーのアドレスを取得することも禁止されています。