付録 A は、Sun が XDR (External Data Representation: 外部データ表現) 標準規格に準拠して開発したライブラリルーチンセットについてのテクニカルノートです。XDR ライブラリルーチンを使用すると、C プログラマは任意のデータ構造をマシンに依存しない形式で記述できます。
XDR は Sun のリモートプロシージャコール (RPC) パッケージの中枢です。 RPC 用データはこの標準規格を使って転送されます。複数の異なるタイプのマシンからアクセスされる (読み書きされる) データの転送には、XDR ライブラリルーチンを使用する必要があります。
XDR は異なる言語、異なるオペレーティングシステム、異なるマシンアーキテクチャの間で機能します。ほとんどのユーザー (特に RPC ユーザー) は、「整数フィルタ」、「浮動小数点フィルタ」、「列挙型フィルタ」の節を読むだけで十分でしょう。RPC と XDR を別のマシン上に実装するプログラマは、このテクニカルノートやプロトコル仕様を読んでください。
RPC 呼び出しを行わない場合でも、rpcgen を使用して XDR ルーチンを書くことができます。
XDR ルーチンを使用する C プログラムはファイル <rpc/xdr.h> をインクルードする必要があります。<rpc/xdr.h> には、必要な XDR システムとのインタフェースがすべて入っています。ライブラリ libnsl.a にはすべての XDR ルーチンが含まれているため、コンパイルは次のコマンドで実行します。
example% cc program.c |
移植性を保つためには、各環境でさまざまな基準を守ってプログラミングしなければなりません。小さなプログラム上の変更の影響は外見上明らかにならない場合もありますが、しばしば、大きな影響を持ちます。次の 2 つのサンプルプログラム (テキスト行の読み込みと書き出しのプログラム) で考えてみましょう。
#include <stdio.h> main() /* writer.c */ { int i; for (i = 0; i < 8; i++) { if (fwrite((char *) &i, sizeof(i), 1, stdout) != 1) { fprintf(stderr, "failed!\n"); exit(1); } } exit(0); }
#include <stdio.h> main() /* reader.c */ { int i, j; for (j = 0; j < 8; j++) { if (fread((char *) &i, sizeof(i), 1, stdin) != 1) { fprintf(stderr, "failed!\n"); exit(1); } printf("%ld ", i); } printf("\n"); exit(0); }
lint チェックにパスしている
どのハードウェアアーキテクチャ上でローカルに実行した場合も同じ動作を示す
writer プログラムの出力を reader プログラムにパイプした場合も、SPARC と Intel マシンの両方で同じ結果が得られます。
sun% writer | reader 0 1 2 3 4 5 6 7 sun% intel% writer | reader 0 1 2 3 4 5 6 7 intel%
ローカルエリアネットワークと 4.2BSD の出現に伴って、「ネットワークパイプ」の概念が導入されました。すなわち、あるマシン上のプロセスで生成したデータを、別のマシン上のプロセスが使用するという概念です。writer とreader を使用してネットワークパイプを構築できます。次は SPARC システム上で writer がデータを生成し、そのデータを Intel アーキテクチャ上で reader が使用した場合です。
sun% writer | rsh intel reader 0 16777216 33554432 50331648 67108864 83886080 100663296 117440512 sun%
Intel 上で writer 、SPARC 上で reader を実行した場合も同一の結果となります。 この原因は、Intel と SPARC の int 型整数は、ワードサイズは同じでもデータのバイト順序が異なるためです。
16777216 は 224 です。4 バイトを逆順にすると、24 番目のビットに 1 が置かれます。
複数の異なるタイプのマシンでデータを共有するときは、データの移植性についての注意が必要です。read() と write() 呼び出しを XDR ライブラリルーチンの呼び出しで xdr_int() に置き換えることにより、データを移植可能にすることができます。xdr_int() は、int 型の整数の外部用の標準的な表現を扱うことができるフィルタです。次のコーディング例では、writer の改訂バージョンを示します。
#include <stdio.h> #include <rpc/rpc.h> /* XDR は RPC のサブライブラリ */ main() /* writer.c */ { XDR xdrs; int i; xdrstdio_create(&xdrs, stdout, XDR_ENCODE); for (i = 0; i < 8; i++) { if (!xdr_int(&xdrs, &i)) { fprintf(stderr, "failed!\n"); exit(1); } } exit(0); }
次のコーディング例は reader の改訂バージョンです。
#include <stdio.h> #include <rpc/rpc.h> /* xdr は rpc のサブライブラリ */ main() /* reader.c */ { XDR xdrs; int i, j; xdrstdio_create(&xdrs, stdin, XDR_DECODE); for (j = 0; j < 8; j++) { if (!xdr_int(&xdrs, &i)) { fprintf(stderr, "failed!\n"); exit(1); } printf("%ld ", i); } printf("\n"); exit(0); }
変更したプログラムを SPARC システム上で実行した結果、Intel 上で実行した結果、および SPARC から Intel にパイプした結果を次に示します。
sun% writer | reader 0 1 2 3 4 5 6 7 sun% intel% writer | reader 0 1 2 3 4 5 6 7 intel% sun% writer | rsh intel reader 0 1 2 3 4 5 6 7 sun%
どのデータ構造体にも移植性の問題が生じる場合があり、特にデータ境界とポインタが大きな問題になります。ワード境界の違いにより、マシンごとに構造体のサイズが異なります。ポインタは使用するには便利ですが、ポインタが定義されたマシンの外部では何の意味も持ちません。
XDR のアプローチは、データを 1 つの標準形式に統一するというものです。すなわち、XDR では、1 つのバイト順序、1 つの浮動小数点形式 (IEEE)、というように標準形式が定義されています。どのマシンで実行するプログラムでも XDR を使用すれば、データがローカルデータ形式から XDR 標準データ形式に変換されるので、移植可能なデータを作成できます。同様に、どのマシンで実行するプログラムでも XDR を使用すれば、データが XDR 標準データ形式からローカルデータ形式に変換されるので、移植可能なデータを読み込むことができます。
1 つの標準形式を使用することにより、移植可能データを作成し送信するプログラムと、移植可能データを受信し使用するプログラムとが完全に切り離されます。新たなマシンは標準データ形式とローカルデータ形式の変換方法を「学習」します。 新たなマシンでは、他のマシンのローカルデータ形式を学ぶ必要がありません。これと同様に、他のマシン上で実行中の既存のプログラムグループでも、新たなマシンのローカルデータ形式を学ぶ必要がありません。新たなマシンが生成した移植可能なデータは、既存プログラムが既に理解している標準データ形式に従っているので、そのまま読み込むことができるからです。
標準データ形式によるアプローチは、XDR 以前より一般に行われていた方法です。 たとえば、TCP/IP、UDP/IP、XNS、Ethernet、および、ISO の OSI 参照モデルの第 5 層より下のプロトコルはすべて標準プロトコルです。標準化アプローチの利点は単純であることです。XDR の場合も、一連の変換ルーチンを一度書いてしまえばそれをずっと使用できます。
標準化アプローチの欠点は、同一のバイト順序を持つ 2 つのマシン間でデータを転送する際に、本来ならば必要ない XDR 標準形式への変換、また、XDR 標準形式からの変換が必要となる点です。 たとえば、バイト順序が Intel の 2 つのマシン間で XDR 標準を使用して整数データを伝送するとします。送信側のマシンでは、整数データのバイト順を Intel から XDR のバイト順序に変換し、受信側のマシンではその反対の変換を行います。 この 2 つのマシンのバイト順序は同じなので、本来このような変換は不要です。
標準データ形式とローカルデータ形式との変換に要する時間は重要ではありません。特に、分散型アプリケーションの場合は大した問題にはなりません。データ構造体を伝送する準備に要する時間のほとんどは、データ変換ではなく、データ構造体の各要素を取り出すのにかかります。
たとえば、ツリー構造を伝送するには、葉の部分をすべてたどって、リーフレコード内の各要素をバッファにコピーして境界を合わせます。葉の部分を格納した記憶領域はその後解放しなければなりません。同じように、ツリー構造を受信する場合も、各リーフへ記憶領域を割り当て、バッファからリーフへデータを移動し、正しく境界を合わせ、ポインタを構築してリーフを正しく接続する、という作業が必要になります。
どのマシンでも、標準データ形式との変換のあるなしに関係なく、データ構造体の走査とコピーにコストがかかります。分散型アプリケーションではこのような通信オーバヘッド、すなわち、送信側のプロトコル層を下ってネットワークを通り受信側のプロトコル層を上るのに時間がかかるため、標準データ形式とのデータ変換のオーバヘッドは相対的に小さくなります。
XDR ライブラリを使用すると、移植の問題を解決し、C の任意のデータ構造を一貫した、文書化された明確な方法で入出力することができます。このため、ネットワーク上のマシン間でデータを共有しない場合でも XDR ライブラリは有用です。
XDR ライブラリには、いくつか例を挙げてみるだけでも、文字列 (NULL で終わるバイト配列)、構造体、共用体、配列に対するフィルタルーチンがあります。ユーザーはさまざまなより基本的なルーチンを使用して、独自の XDR ルーチンを作成し、任意のデータ構造体 (配列の要素、共用体のアーム、他の構造体からポイントされるオブジェクト) を記述できます。構造体自体にも、任意の要素の配列や他の構造体へのポインタを持たせることができます。
例 A–3 および例 A–4 を注意して見てください。XDR ストリーム作成ルーチンファミリの各メンバー間でビットストリームの扱いが異なります。この例では、標準入出力ルーチンを使用してデータを操作しているので、xdrstdio_create() を使用します。XDR ストリーム作成ルーチンに渡す引数は、ルーチンの機能によって異なります。サンプルプログラムでは、初期化する XDR 構造体へのポインタ、入出力を行う FILE へのポインタ、処理内容の 3 つを引数として渡しています。処理内容は、writer プログラムではシリアライズするので XDR_ENCODE、reader プログラムではデシリアライズするので XDR_DECODE を指定します。
RPC ユーザーは XDR ストリームを作成する必要はありません。RPC システムでストリームが作成され、ユーザーに引き渡されます。
xdr_int() プリミティブは、ほとんどの XDR ライブラリプリミティブと、すべてのクライアント作成の XDR ルーチンに共通の特徴を持っています。第 1 に、このルーチンはエラーが起こると FALSE(0) を返し、正常終了すると TRUE(1) を返します。第 2 に、各データ型 xxx
ごとに対応する XDR ルーチンがあります。 XDR ルーチンの形式を次に示します。
xdr_xxx(xdrs, xp) XDR *xdrs; xxx *xp; { } |
このサンプルプログラムの場合は、xxx
は int ですので、対応する XDR プリミティブは xdr_int() になります。クライアントも同様にして、任意の構造体 xxx を定義し、対応するルーチン xdr_int() を作成して、個々のフィールドごとにデータ型に一致する XDR ルーチンを呼び出します。どのデータ型の場合も、最初のパラメータ xdrs は隠されたハンドルとして、そのままプリミティブに渡します。
XDR ルーチンは両方向の変換を行います。すなわち、データをシリアライズするときもデシリアライズするときも同じルーチンを呼び出します。この機能は、移植可能データのソフトウェアエンジニアリングには不可欠な機能です。これは両方の操作に同じルーチンを呼び出すというものです。 この方法によりシリアライズされたデータのデシリアライズがほぼ保証されます。
ネットワークデータを生成するときも使用するときも同じルーチンが使用できます。このコンセプトは、常に、オブジェクト自体ではなく、オブジェクトのアドレスを渡すことにより実装されます。デシリアライズの場合のみ、オブジェクトは変更されます。 この機能は、サンプルプログラムではわかりませんが、より複雑なデータ構造体をマシン間で引き渡すときに、この機能の価値が明らかになります。必要な場合は、XDR の処理内容を取り出すこともできます。詳細は、処理内容の節を参照してください。
もう少し複雑な例を以下に示します。ある人の総資産と負債のデータをプロセス間で交換するとします。このデータは、次のような独自のデータ型が必要なほど重要であるとします。
struct gnumbers { int g_assets; int g_liabilities; }; |
このデータ型に対応する XDR ルーチンでは、この構造体を次のように記述します。
bool_t /* 正常終了では TRUE、異常終了では FALSE */ xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { if (xdr_int(xdrs, &gp->g_assets) && xdr_int(xdrs, &gp->g_liabilities)) return(TRUE); return(FALSE); } |
引数 xdrs は値を調べたり変更したりすることなく、そのままサブコンポーネントルーチンに渡していることに注意してください。このパラメータは単にサブコンポーネントルーチンに渡されているだけです。 サブルーチンが異常終了した場合は、ルーチンは各 XDR 呼び出しの戻り値を調べ、すぐに停止し、FALSE を返さなくてはなりません。
この例では、bool_t 型が TRUE (1) と FALSE (0) のどちらかの値だけをとる整数として宣言されていることがわかります。 このマニュアルでは次の定義を使用します。
#define bool_t int #define TRUE 1 #define FALSE 0 |
これらの定義を使用し、xdr_gnumbers () を次のように書き換えることができます。
xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { return(xdr_int(xdrs, &gp->g_assets) && xdr_int(xdrs, &gp->g_liabilities)); } |
この節では、XDR プリミティブの概要を述べます。始めにメモリー割り当てと基本データ型を説明し、次に合成データ型を説明します。最後に、XDR ユーティリティについて説明します。XDR のプリミティブとユーティリティのインタフェースはインクルードファイルで定義されています。<rpc/xdr.h> は <rpc/rpc.h> から自動的にインクルードされます。
XDR ルーチンを使用するときは、前もってメモリーを割り当てておかなければならない場合や、必要なメモリーサイズを決定しておかなければならない場合があります。 XDR 変換ルーチン用のメモリーの割り当てと割り当て解除を制御する必要がある場合は、 xdr_sizeof() ルーチンを使用します。このルーチンは XDR フィルタ関数の 1 つ (func()) を使用して、データの符号化と復号化に必要なバイト数を返します。 xdr_sizeof() 関数が返す値には、RPC ヘッダーやレコードマーカーは含まれません。必要なメモリーサイズを正確に計算するにはこれらのバイト数も追加しなければなりません。 エラーが起こった場合、xdr_sizeof() はゼロを返します。
xdr_sizeof(xdrproc_t func, void *data) |
RPC 環境外で XDR を使用するアプリケーションでメモリーを割り当てるときや、通信プロトコルを選択するとき、下位レベルの RPC でクライアント作成関数とサーバー作成関数を実行するときに、xdr_sizeof() を使用してください。
次の 2 つのコーディング例は xdr_sizeof () の 2 つの使用例を示しています。
#include <rpc/rpc.h> /* * この関数への入力引数は、CLIENT ハンドル、XDR 関数、XDR 変換を行う * データへのポインタです。XDR 変換を行うデータが、CLIENT ハンドルに * 結合しているトランスポートで送信可能な大きさの場合は TRUE、 * 大き過ぎて送信不可能な場合は FALSE を返します。 */ bool_t cansend(cl, xdrfunc, xdrdata) CLIENT *cl; xdrproc_t xdrfunc; void *xdrdata; { int fd; struct t_info tinfo; if (clnt_control(cl, CLGET_FD, &fd) == -1) { /* ハンドルの clnt_control() エラー */ return (FALSE); } if (t_getinfo(fd, &tinfo) == -1) { /* ハンドルの t_getinfo() エラー */ return (FALSE); } else { if (tinfo.servtype == T_CLTS) { /* * 現在使用しているのは非接続型トランスポートです。 * xdr_sizeof() を使用して、メモリー要求がこのトランスポートでは * 大き過ぎないか調べます。 */ switch(tinfo.tsdu) { case 0: /* TSDU の概念なし */ case -2: /* 通常データの送信不可能 */ return (FALSE); break; case -1: /* TSDU サイズの制限なし */ return (TRUE); break; default: if (tinfo.tsdu < xdr_sizeof(xdrfunc, xdrdata)) return (FALSE); else return (TRUE); } } else return (TRUE); } }
次は xdr_sizeof() の 2 つめの使用例です。
#include <sys/statvfs.h> #include <sys/sysmacros.h> /* * この関数への入力引数は、ファイル名、XDR 関数、XDR 変換を行うデータへの * ポインタです。この関数は、ファイルが置かれているファイルシステムに、 * データを XDR 変換するのに必要な空間が残っていれば TRUE を返します。 * ファイルシステムに関して statvfs(2) で得られる情報はブロック数単位 * なので、xdr_sizeof() の戻り値もバイト数からディスクブロック数に * 変換しなければならないことに注意してください。 */ bool_t canwrite(file, xdrfunc, xdrdata) char *file; xdrproc_t xdrfunc; void *xdrdata; { struct statvfs s; if (statvfs(file, &s) == -1) { /* ハンドルの statvfs() エラー */ return (FALSE); } if (s.f_bavail>= btod(xdr_sizeof(xdrfunc, xdrdata))) return (TRUE); else return (FALSE); }
XDR ライブラリでは、整数を対応する外部表現に変換するプリミティブが提供されます。プリミティブが変換の対象とする整数は、次の組み合わせで表されます。
[signed, unsigned] * [short, int, long] |
具体的には次の 8 つのプリミティブが提供されています。
bool_t xdr_char(xdrs, op) XDR *xdrs; char *cp; bool_t xdr_u_char(xdrs, ucp) XDR *xdrs; unsigned char *ucp; bool_t xdr_int(xdrs, ip) XDR *xdrs; int *ip; bool_t xdr_u_int(xdrs, up) XDR *xdrs; unsigned *up; bool_t xdr_long(xdrs, lip) XDR *xdrs; long *lip; bool_t xdr_u_long(xdrs, lup) XDR *xdrs; u_long *lup; bool_t xdr_short(xdrs, sip) XDR *xdrs; short *sip; bool_t xdr_u_short(xdrs, sup) XDR *xdrs; u_short *sup;
最初の引数 xdrs は、XDR のストリームハンドルです。第 2 引数は、ストリームへ渡すデータのアドレス、または、ストリームからデータを受け取るアドレスです。どのルーチンも、変換に成功すれば TRUE、失敗すれば FALSE を返します。
XDR ライブラリでは、C の浮動小数点型データのプリミティブも提供されます。
bool_t xdr_float(xdrs, fp) XDR *xdrs; float *fp; bool_t xdr_double(xdrs, dp) XDR *xdrs; double *dp;
最初の引数 xdrs は、XDR のストリームハンドルです。第 2 引数は、ストリームへ渡す浮動小数点データのアドレスまたはストリームから浮動小数点データを受け取るアドレスです。どちらのルーチンも、変換に成功すれば TRUE、失敗すれば FALSE を返します。
数値の表現形式は、浮動小数点に関する IEEE 標準規約に従っているため、IEEE 準拠の表現形式からマシン固有の表現形式に復号化したり、暗号化したりすると、エラーが起こる場合があります。
XDR ライブラリでは、一般の列挙型に対するプリミティブを提供しています。このプリミティブでは、C の enum
型のマシン内部表現が C の整数と同じであるとみなしています。ブール型は enum
型の重要な一例です。ブール値の外部表現は常に TRUE (1) と FALSE (0) です。
#define bool_t int #define FALSE 0 #define TRUE 1 #define enum_t int bool_t xdr_enum(xdrs, ep) XDR *xdrs; enum_t *ep; bool_t xdr_bool(xdrs, bp) XDR *xdrs; bool_t *bp; |
第 2 パラメータ ep と bp は、ストリーム xdrs へ渡すデータのアドレス、または、ストリーム xdrs からデータを受け取るアドレスです。
データが一切渡されず要求されていなくても、XDR ルーチンを RPC システムに提供しなければならない場合があります。ライブラリでは、そのためのルーチンを提供しています。
bool_t xdr_void(); /* 常に TRUE を返す */ |
合成データ型、または、複合データ型を変換するプリミティブは、これまでに説明したプリミティブより多くの引数を必要とし、より複雑な機能を実行します。この節では、文字列、配列、共用体、構造体へのポインタに対するプリミティブを説明します。
合成データ型のプリミティブでは、メモリー管理を使用する場合があります。多くの場合、XDR_DECODE を指定してデータをデシリアライズすると、メモリーが割り当てられます。そのため、XDR パッケージではメモリー割り当てを解除する方法を提供しなければなりません。XDR 処理 XDR_FREE がこの方法を提供します。XDR の 処理内容には、XDR_ENCODE、XDR_DECODE、XDR_FREE の 3 つがあります。
C 言語では、文字列が、NULL コードで終わるバイトシーケンスと定義されています。NULL コードは、文字列の長さを求めるときは計算に入れません。ところが、文字列を引き渡したり操作したりするときは、文字へのポインタが使用されます。そのため、XDRライブラリでは文字列を文字シーケンスではなく、char *
と定義しています。文字列の外部表現は、内部表現とは大きく異なります。
外部では、文字列は一連の ASCII 文字で表現されます。内部では、文字列は文字ポインタで表現されます。 この 2 つの表現形式の間の変換は、ルーチン xdr_string() で実行します。
bool_t xdr_string(xdrs, sp, maxlength) XDR *xdrs; char **sp; u_int maxlength; |
最初の引数 xdrs は、XDR のストリームハンドルです。第 2 引数 sp は文字列へのポインタ (データ型は char **) です。第 3 引数 maxlength は、符号化または復号化の対象とする最大バイト数です。通常、この値はプロトコルで決まります。たとえば、あるプロトコル仕様では、ファイル名は最大 255 文字までとされています。文字数が maxlength の値を超えていれば FALSE、超えていなければ TRUE が返されます。
xdr_string() の機能は、この節でこれまでに説明した他の変換ルーチンと同様です。たとえば、 XDR_ENCODE 処理では、引数 sp は一定の長さの文字列を指します。 この文字列の長さが maxlength を超えていなければ、この文字列がシリアライズされます。
デシリアライズの場合はもう少し複雑です。最初に、ストリームから取り込む文字列の長さを決定します。文字列の長さは maxlength を超えることはできません。次に sp を間接参照します。その値が NULL の場合は、適切なサイズの文字列を割り当てて、*sp がその文字列を指すように設定します。*sp の元々の値が NULL でない場合は、デシリアライズしたデータを入れるターゲットエリアが既に割り当てられており、maxlength 以下の長さの文字列をそこに格納できるものとみなします。どちらの場合も、文字列が復号化されてターゲットエリアに保存されます。次に、文字列の最後に NULL コードが付加されます。
XDR_FREE 処理の場合は、sp を間接参照して文字列を取り出します。その文字列が NULL 文字列でなければ、領域を解放して *sp を NULL に設定します。この処理を実行するときは、xdr_string() は引数 maxlength を無視します。
空の文字列 ("") を XDR 変換することはできますが、NULL 文字列を XDR 変換はできません。
文字列よりも可変長バイト配列を使用する方が便利な場合があります。バイト配列は、次の 3 つの点で文字列と異なっています。
配列の長さ、バイト数、符号なし整数内のバイト数。
バイトシーケンスが NULL で終わらない。
バイトの外部表現と内部表現が同じ。
バイト配列の内部表現と外部表現との変換には、プリミティブ xdr_bytes() を使用します。
bool_t xdr_bytes(xdrs, bpp, lp, maxlength) XDR *xdrs; char **bpp; u_int *lp; u_int maxlength; |
このルーチンの第 1、第 2、第 4 引数はそれぞれ、xdr_string() の第 1、第 2、第 3 引数と同じです。シリアライズの場合は、lp を間接参照してバイトシーケンスの長さを得ます。 デシリアライズの場合は、*lp にバイトシーケンスの長さが設定されます。
XDR ライブラリパッケージでは、任意の要素で構成される配列を処理するプリミティブが提供されています。xdr_bytes() ルーチンは一般の配列のサブセットを処理します。すなわち、xdr_bytes() ルーチンでは、配列要素のサイズは 1 に決まっており、各要素の外部記述も組み込まれています。一般の配列に対するプリミティブ xdr_array() の引数は、xdr_bytes() の引数より 2 つ多く、配列要素のサイズと、各要素を変換する XDR ルーチンとが渡されます。渡された XDR ルーチンは、配列の各要素の符号化または復号化のときに呼び出されます。
bool_t xdr_array(xdrs, ap, lp, maxlength, elementsize, xdr_element) XDR *xdrs; char **ap; u_int *lp; u_int maxlength; u_int elementsize; bool_t (*xdr_element)(); |
引数 ap は、配列へのポインタのアドレスです。配列をデシリアライズするときに *ap が NULL の場合は、適切なサイズの配列が割り当てられ、*ap はその配列を指すように設定されます。配列をシリアライズするときは、配列の要素数を *lp から取り出します。 配列をデシリアライズするときは、*lp には配列の長さが設定されます。引数 maxlength は、配列に入れることができる最大要素数です。 elementsiz は、配列の各要素のサイズ (バイト数) です。C の sizeof() 関数を使用してこの値を調べることができます。xdr_element() ルーチンは、配列の各要素のシリアライズ、デシリアライズ、解放を行うときに呼び出されます。
他の合成データ型を定義する前に、3 つの例を示します。
ネットワーク上のマシンのユーザーは次に基づいて特定できます。
マシン名
ユーザーの UID。これについては、マニュアルページ getuid(2) を参照してください。
ユーザーが所属するグループ番号。マニュアルページ getgroups(2) を参照してください。
struct netuser { char *nu_machinename; int nu_uid; u_int nu_glen; int *nu_gids; }; #define NLEN 255 /* マシン名は 255 文字以下 */ #define NGRPS 20 /* ユーザーが所属するグループ数は 20 以下 */ bool_t xdr_netuser(xdrs, nup) XDR *xdrs; struct netuser *nup; { return(xdr_string(xdrs, &nup->nu_machinename, NLEN) && xdr_int(xdrs, &nup->nu_uid) && xdr_array(xdrs, &nup->nu_gids, &nup->nu_glen, NGRPS, sizeof (int), xdr_int)); } |
ネットワークユーザーのグループを netuser 構造体の配列で表すことができます。次のコーディング例では宣言およびそれに関連付けられた XDR ルーチンを示します。
struct party { u_int p_len; struct netuser *p_nusers; }; #define PLEN 500 /* グループに所属するユーザー数の上限 */ bool_t xdr_party(xdrs, pp) XDR *xdrs; struct party *pp; { return(xdr_array(xdrs, &pp->p_nusers, &pp->p_len, PLEN, sizeof (struct netuser), xdr_netuser)); }
main に対するよく知られた引数 argc と argv を組み合わせて構造体を作成します。 これらの構造体の配列でコマンドヒストリを作成できます。構造体の宣言とその XDR ルーチンは次の例のようになります。
struct cmd { u_int c_argc; char **c_argv; }; #define ALEN 1000 /* argc は 1000 以下 */ #define NARGC 100 /* 各コマンドの args は 100 以下 */ struct history { u_int h_len; struct cmd *h_cmds; }; #define NCMDS 75 /* ヒストリは 75 コマンドまで */ bool_t xdr_wrapstring(xdrs, sp) XDR *xdrs; char **sp; { return(xdr_string(xdrs, sp, ALEN)); } bool_t xdr_cmd(xdrs, cp) XDR *xdrs; struct cmd *cp; { return(xdr_array(xdrs, &cp->c_argv, &cp->c_argc, NARGC, sizeof (char *), xdr_wrapstring)); } bool_t xdr_history(xdrs, hp) XDR *xdrs; struct history *hp; { return(xdr_array(xdrs, &hp->h_cmds, &hp->h_len, NCMDS, sizeof (struct cmd), xdr_cmd)); }
このプログラムの複雑な点は、xdr_string() を呼び出すためのルーチン xdr_wrapstring() が必要な点です。xdr_array() が配列要素記述ルーチンを呼び出すときは引数が 2 つしか渡されないため、xdr_string() の第 3 パラメータを提供するルーチン xdr_wrapstring() が必要になります。xdr_wrapstring() が xdr_string() へ第 3 パラメータを提供します。
これまでの説明で XDR ライブラリの再帰的性質が明らかになりました。合成データ型についてさらに説明します。
プロトコルによっては、サーバーからクライアントにハンドルが渡され、クライアントは後からハンドルをサーバーに送り返します。クライアントではハンドルの内容を調べることはなく、受け取ったものをそのまま送り返します。すなわち、ハンドルは隠されたデータ (内容が隠されたデータ) です。固定長の隠されたデータを記述するには、xdr_opaque() プリミティブを使用します。
bool_t xdr_opaque(xdrs, p, len) XDR *xdrs; char *p; u_int len; |
引数 p は隠されたオブジェクトのアドレスを、 len は隠されたオブジェクト内のバイト数を示します。隠されたデータの定義からすると、実際に隠れたオブジェクトに入っているデータはマシン間で移植不可能です。
SunOS/SVR4 システムには、隠されたデータの操作用にもう 1 つのルーチンが提供されています。そのルーチン xdr_netobj は xdr_opaque() と同様にカウント付きの隠されたデータを送信します。次のコーディング例は xdr_netobj() の構文を示します。
struct netobj { u_int n_len; char *n_bytes; }; typedef struct netobj netobj; bool_t xdr_netobj(xdrs, np) XDR *xdrs; struct netobj *np; |
xdr_netobj() はフィルタプリミティブで、可変長の隠されたデータとその外部表現との変換を行います。引数 np は netobj 構造体のアドレスです。netobj 構造体には隠されたデータの長さと隠されたデータへのポインタが入っています。 長さは、MAX_NETOBJ_SZ バイトを超えることはできません。変換に成功すれば TRUE、失敗すれば FALSE が返されます。
XDR ライブラリは、次のサンプルプログラムのように、固定長配列用のプリミティブ xdr_vector() を提供します。
#define NLEN 255 /* マシン名は 255 文字以下 */ #define NGRPS 20 /* ユーザーは正確に 20 のグループに所属 */ struct netuser { char *nu_machinename; int nu_uid; int nu_gids[NGRPS]; }; bool_t xdr_netuser(xdrs, nup) XDR *xdrs; struct netuser *nup; { int i; if (!xdr_string(xdrs, &nup->nu_machinename, NLEN)) return(FALSE); if (!xdr_int(xdrs, &nup->nu_uid)) return(FALSE); if (!xdr_vector(xdrs, nup->nu_gids, NGRPS, sizeof(int), xdr_int)) return(FALSE); return(TRUE); }
XDR ライブラリは識別型の共用体もサポートしています。識別型の共用体は、C の共用体に、共用体の「アーム」を選択する enum_t型の値が付加されたものです。
struct xdr_discrim { enum_t value; bool_t (*proc)(); }; bool_t xdr_union(xdrs, dscmp, unp, arms, defaultarm) XDR *xdrs; enum_t *dscmp; char *unp; struct xdr_discrim *arms; bool_t (*defaultarm)(); /* NULL と同じ */
このルーチンでは、最初に *dscmp にある要素識別子を変換します。要素識別子は常に enum_t 型です。次に、*unp にある共用体が変換されます。引数 arms は xdr_discrim 構造体配列へのポインタです。各構造体には、[value,proc] のペアが入っています。共用体の要素識別子が対応する value と一致すれば、それに対する proc() が呼び出されて共用体が変換されます。xdr_discrim 構造体配列の終わりは、ルーチンの値が NULL(0) なので判別できます。arms 配列の中に要素識別子に一致するものがない場合は、defaultarm() 手続きが NULL でなければそれが呼び出されます。NULL の場合は FALSE を返します。
共用体のデータ型は、整数、文字へのポインタ (文字列)、gnumbers 構造体のどれかで、共用体と現在の型は構造体で宣言されています。 宣言は次のようになります。
enum utype {INTEGER=1, STRING=2, GNUMBERS=3}; struct u_tag { enum utype utype; /* 共用体の要素識別子 */ union { int ival; char *pval; struct gnumbers gn; } uval; };
次のサンプルプログラムでは、XDR プロシージャを構築し、識別型の共用体をデシリアライズします。
struct xdr_discrim u_tag_arms[4] = { {INTEGER, xdr_int}, {GNUMBERS, xdr_gnumbers} {STRING, xdr_wrapstring}, {__dontcare__, NULL} /* arm の最後は常に NULL の xdr_proc */ } bool_t xdr_u_tag(xdrs, utp) XDR *xdrs; struct u_tag *utp; { return(xdr_union(xdrs, &utp->utype, &utp->uval, u_tag_arms, NULL)); } |
ルーチン xdr_gnumbers() は XDR ライブラリ 内で説明しました。 この例では、xdr_union() のデフォルトの arm 引数 (最後の引数) には、 NULL を渡しています。 したがって、共用体の要素識別子の値は、u_tag_arms 配列に表示された値のどれかになります。例 A–12 もまたアームの配列内要素をソートする必要がないことを示しています。
要素識別子は、この例では連続した値をとっていますが、連続しない値でも構いません。要素識別子の型の各要素に、明示的に整数値を割り当てておくと、要素識別子の外部表現として明記でき、異なる C コンパイラでも要素識別子が同じ値で出力されることが保証されます。
C では構造体内に別の構造体へのポインタをおくと便利な場合があります。 プリミティブ xdr_reference() を使用すると、そのように参照される構造体を簡単にシリアライズ (またはデシリアライズ) できます。
bool_t xdr_reference(xdrs, pp, size, proc) XDR *xdrs; char **pp; u_int ssize; bool_t (*proc)(); |
引数 pp は構造体へのポインタのアドレスです。引数 ssize は構造体のサイズ (バイト数) です。C の sizeof() 関数を使用してこの値を調べることができます。引数 proc() は構造体を記述する XDR ルーチンです。データを復号化するとき、*pp が NULL の場合は記憶領域が割り当てられます。
プリミティブ xdr_struct() では、ポインタだけで十分であるため、構造体内で構造体を記述する必要はありません。
人の名前、および、その人の総資産と負債の入った gnumbers 構造体へのポインタとで構成される次のような構造体があるとします。
struct pgn { char *name; struct gnumbers *gnp; }; |
これに対する XDR ルーチンは次のようになります。
bool_t xdr_pgn(xdrs, pp) XDR *xdrs; struct pgn *pp; { return(xdr_string(xdrs, &pp->name, NLEN) && xdr_reference(xdrs, &pp->gnp, sizeof(struct gnumbers), xdr_gnumbers)); } |
C のプログラマは多くのアプリケーションで、ポインタの値に 2 つの意味を持たせています。一番多い例としては、ポインタ値が NULL (ゼロ) の場合はデータが不要なことを意味する方法ですが、アプリケーションごとにさまざまな解釈ができます。実際に C プログラマは、ポインタ値の解釈をオーバロードすることにより、効率よく識別型共用体を実現しています。たとえば、gnp のポインタ値を NULL にすれば、その人の総資産と負債が未知であると解釈できます。すなわち、ポインタ値には 2 つの意味があります。データがあるかどうか、ある場合はメモリーのどこにあるかを示します。リンクリストは、アプリケーション固有のポインタ解釈の極端な例です。
プリミティブ xdr_reference() はシリアライズの際に、値が NULL のポインタに特別な意味を持たせることができません。そのため、データをシリアライズするときに、xdr_reference() に値が NULL のポインタのアドレスを渡すと、多くの場合メモリーフォルトを引き起こし、UNIX システムではコアダンプが起こります。
xdr_pointer() では NULL ポインタも正しく処理できます。
この節で説明するプリミティブを使って XDR ストリームを操作することができます。
u_int xdr_getpos(xdrs) XDR *xdrs; bool_t xdr_setpos(xdrs, pos) XDR *xdrs; u_int pos; xdr_destroy(xdrs) XDR *xdrs;
ルーチン xdr_getpos() は データストリーム内の現在の位置を記述する符号なし整数を返します。
XDR ストリームの中には、xdr_getpos()の返す値が意味を持たないものがあります。その場合は、-1 が返されます (ただし、-1 も正当な値です)。
ルーチン xdr_setpos() はストリーム位置を pos に設定します。 XDR ストリームの中には、xdr_setpos() で位置設定ができないものもあります。その場合は、FALSE が返されます。指定した位置が適用範囲外の場合もエラーとなります。設定位置の適用範囲はストリームごとに異なります。
XDR ストリームを廃棄するには、プリミティブ xdr_destroy() を使用します。このルーチンを呼び出した後で、ストリームを使用した場合の動作は不定です。
XDR_ENCODE、 XDR_DECODE、または XDR_FREE 処理内容を利用して、XDR ルーチンを最適化したいと考える場合もあるでしょう。XDR の処理内容は、常に xdrs->x_op に入っています。リンクリスト の節には、xdrs->x_op フィールドを活用するサンプルプログラムが示されています。
XDR ストリームは、適切な作成ルーチンを呼び出すことで得られます。作成ルーチンへの引数でストリームのさまざまな特性が決まります。現在、データのシリアライズとデシリアライズに使用できるストリームには、標準入出力 FILE ストリーム、レコードストリーム、UNIX ファイル、メモリーがあります。
XDR ストリームは、xdrstdio_create() ルーチンを使用して標準入出力とのインタフェースをとることができます。
#include <stdio.h> #include <rpc/rpc.h> /* XDR は RPC のサブセット */ void xdrstdio_create(xdrs, fp, xdr_op) XDR *xdrs; FILE *fp; enum xdr_op x_op; |
xdrstdio_create() は、xdrs が指す XDR ストリームを初期化します。XDR ストリームは、標準入出力ライブラリとのインタフェースが可能です。引数 fp はオープンしているファイル、x_op は XDR の処理内容です。
メモリーストリームを作成すると、メモリーの特定領域とのデータストリームが使用できます。
#include <rpc/rpc.h> void xdrmem_create(xdrs, addr, len, x_op) XDR *xdrs; char *addr; u_int len; enum xdr_op x_op; |
xdrmem_create() ルーチンは、ローカルメモリー内の XDR ストリームを初期化します。引数 addr は使用するメモリーを指します。パラメータ len はメモリーの大きさ (バイト数) です。引数 xdrs と x_op は、xdrstdio_create() の同名の引数と同じです。現在、RPC ではデータグラムの実現に xdrmem_create() を使用しています。TLI ルーチン t_sndndata() を呼び出す前に、完全な呼び出しメッセージ (または応答メッセージ) がメモリーに構築されます。
レコードストリームは、レコードマーク標準の上に構築される XDR ストリームです。レコードマーク標準は、UNIX ファイル、または、4.2 BSD 接続インタフェースの上に構築されます。
#include <rpc/rpc.h> /* XDR は RPC のサブセット */ xdrrec_create(xdrs, sendsize, recvsize, iohandle, readproc, writeproc) XDR *xdrs; u_int sendsize, recvsize; char *iohandle; int (*readproc)(), (*writeproc)();
xdrrec_create() は、任意の長さの双方向レコードシーケンスが可能な XDR ストリームインタフェースを提供します。レコードの内容は、XDR 形式のデータと考えられます。レコードストリームは、主に RPC と TCP 接続とのインタフェースとして使用されますが、通常の UNIX ファイルとのデータ入出力にも使用できます。
引数 xdrs は、上に説明した同名の引数と同じです。レコードストリームでは、標準入出力ストリームと同様のデータバッファリングを行います。引数 sendsize と recvsize には、それぞれ送信バッファと受信バッファのサイズ (バイト数) を指定します。この値がゼロ (0) の場合は、あらかじめ指定されたデフォルトのバッファサイズが使用されます。
バッファにデータを読み込んだり、データをフラッシュしたりするときは、ルーチン readproc()または writeproc() を呼び出します。この 2 つのルーチンの使用方法と機能は、UNIXのシステムコール read() と write() に似ていますが、第 1 引数には隠されたデータ iohandle を渡します。その後の 2 つの引数および nbytes と、戻り値 (バイトカウント) はシステムルーチンと同じです。次の xxx()を readproc() または writeproc() とすると、その形式は次のようになります。
/* 実際に伝送したバイト数を返す。エラーのときの戻り値は -1 */ int xxx(iohandle, buf, len) char *iohandle; char *buf; int nbytes; |
XDR ストリームには、バイトストリームのレコードを区切る方法が提供されています。XDR ストリームを作成するのに必要な抽象データ型については、XDR ストリームの作成を参照してください。XDR ストリームレコードを区切るのに使用する RPC プロトコルについては、レコードマーク標準を参照してください。
レコードストリームに特有なプリミティブは次のとおりです。
bool_t xdrrec_endofrecord(xdrs, flushnow) XDR *xdrs; bool_t flushnow; bool_t xdrrec_skiprecord(xdrs) XDR *xdrs; bool_t xdrrec_eof(xdrs) XDR *xdrs;
xdrrec_endofrecord() ルーチンを呼び出すと、現在出力しているデータにレコードマークが付けられます。引数 flushnow に TRUE を指定すると、ストリームの writeproc() が呼び出されます。指定しない場合は出力バッファがいっぱいになったときに writeproc() が呼び出されます。
xdrrec_skiprecord() ルーチンを呼び出すと、入力ストリーム内の現在位置が、現在レコードの境界まで移動し、ストリーム内の次のレコードの始めに位置します。
ストリームの入力バッファにデータがなくなると、xdrrec_eof() ルーチンから TRUE が返されます。ストリームの元のファイル記述子にデータがなくなったという意味ではありません。
この節では、新たな XDR ストリームインスタンスの作成に必要な抽象データ型を示します。
次のサンプルプログラムの構造体は、XDR ストリームへのインタフェースを定義します。
enum xdr_op {XDR_ENCODE=0, XDR_DECODE=1, XDR_FREE=2}; typedef struct { enum xdr_op x_op; struct xdr_ops { bool_t (*x_getlong)(); /* ストリームから long 型データを入力 */ bool_t (*x_putlong)(); /* ストリームに long 型データを出力 */ bool_t (*x_getbytes)(); /* ストリームから複数バイトを入力 */ bool_t (*x_putbytes)(); /* ストリームに複数バイトを出力 */ u_int (*x_getpostn)(); /* ストリームのオフセットを返す */ bool_t (*x_setpostn)(); /* オフセットの再設定 */ caddr_t (*x_inline)(); /* バッファリングされたデータへのポインタ */ VOID (*x_destroy)(); /* プライベートエリアの解放 */ bool_t (*x_control)(); /* クライアント情報の検索、変更 */ bool_t (*x_getint32)(); /* ストリームから int を取得 */ bool_t (*x_putint32)(); /* ストリームに int を出力 */ } *x_ops; caddr_t x_public; /* ユーザーデータ */ caddr_t x_private; /* プライベートデータへのポインタ */ caddr_t x_base; /* 位置情報のためのプライベートデータ */ int x_handy; /* その他のプライベートワード */ } XDR;
x_op フィールドは、現在ストリームに対して実行している処理内容を示します。このフィールドは XDR プリミティブにとっては重要なフィールドですが、ストリームの実現に対しては影響ありません。すなわち、ストリームは、この値に依存した方法で作成しないでください。x_private、x_base、x_handy の各フィールドは、特定のストリームを実現するためのプライベートデータです。x_public は XDR クライアントのためのフィールドでなので、XDR ストリームを実現するために使用したり、XDR プリミティブで使用したりできません。x_getpostn()、x_setpostn()、x_destroy() はストリームへのアクセス操作に使用するマクロです。
x_inline() ルーチンには、XDR * と符号なし整数 (バイトカウント) の 2 つの引数を渡します。このルーチンからは、ストリームの内部バッファセグメントへのポインタが返されます。呼び出し側ではそのバッファセグメントを自由に使用できます。ストリームは、そのバッファセグメント内のバイトは消費されたと解釈します。このルーチンは、要求されたサイズのバッファセグメントを返せない場合は NULL を返します。
x_inline() ルーチンは、バッファを反復して使用するためのもので、このバッファの使用は移植可能ではありません。この機能は使用しないでください。
ストリームに対するバイトシーケンスの単純な入出力には、x_getbytes() と x_putbytes() を使用します。入出力に成功すれば TRUE、失敗すれば FALSE が返されます。この 2 つのルーチンの引数は同じです (xxx は同じ文字列で置換します)。
bool_t xxxbytes(xdrs, buf, bytecount) XDR *xdrs; char *buf; u_int bytecount; |
x_getint32() と x_putint32() の演算により、データストリームから int 数値を受け取ったり、データストリームへ数値を出力したりできます。これらのルーチンはマシン表現形式と外部 (標準) 表現形式の間で整数を変換します。変換には、UNIX のプリミティブ htonl() と ntohl() を利用できます。上位レベルの XDR 対応ソフトウェアでは、符号付きと符号なしの整数のビット数は同じで、負にならない整数のビットパターンは符号なしの整数のビットパターンと同じであるとみなします。入出力に成功すれば TRUE 、失敗すれば FALSE が返されます。
関数 x_getint() と x_putint() では、これらの演算を使用します。この 2 つのルーチンの引数は同じです。
bool_t xxxint(xdrs, ip) XDR *xdrs; int32_t *ip; |
これらの演算の long バージョン (x_getlong() と x_putlong()) でも、 x_getint32() と x_putint32() を呼び出します。この場合、プログラムがどのようなマシンで実行されていても、処理される量は必ず 4 バイトになります。
XDR ストリームを新規開発するときは、作成ルーチンを使用して、新たな操作ルーチンを持つ XDR 構造体を作成し、クライアントに提供しなければなりません。
この節では、データ構造体を引き渡す方法を説明します。そのような構造体の 1 つに、任意の長さのリンクリストがあります。これまでの簡単なサンプルプログラムとは違い、ここでは XDR C ライブラリルーチンと、XDR データ記述言語の両方を使用します。この言語の詳細は 付録 C 「XDR プロトコル仕様」 を参照してください。
ポインタのサンプルプログラムの節では、各個人の総資産と負債に関する C のデータ構造体とそれに対する XDR ルーチンのサンプルプログラムを示しました。次のサンプルプログラムでは、リンクリストを使用して、ポインタのサンプルプログラムと同じものを作成します。
struct gnumbers { int g_assets; int g_liabilities; }; bool_t xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { return(xdr_int(xdrs, &(gp->g_assets) && xdr_int(xdrs, &(gp->g_liabilities))); } |
同じ情報を持つリンクリストを作成します。構造体は次のようになります。
struct gnumbers_node { struct gnumbers gn_numbers; struct gnumbers_node *gn_next; }; typedef struct gnumbers_node *gnumbers_list; |
リンクリストのヘッダーはデータオブジェクトと考えてください。 便宜のため付けられた構造体の単なる短縮形ではありません。 同様に、gn_next フィールドも、オブジェクトの終わりかどうかを示すのに使用されます。残念ながら、オブジェクトが次につながる場合も、gn_next フィールドに続きのアドレスが入ります。オブジェクトをシリアライズするときは、リンクアドレスの情報は役に立ちません。
このリンクリストを XDR データ記述言語で示すと、gnumbers_list の再帰宣言となります。
struct gnumbers { int g_assets; int g_liabilities; }; struct gnumbers_node { gnumbers gn_numbers; gnumbers_node *gn_next; }; |
この記述では、ブール値は次のデータがあるかどうかを示します。ブール値が FALSE の場合は、それが構造体の最終データフィールドとなります。ブール値が TRUE の場合は、その後に gnumbers 構造体と gnumbers_list とが再帰的に続きます。C 宣言内には明示的にブール値が宣言されていませんが、gn_next フィールドは黙示的に情報を示している点に注意してください、XDR データ記述では明示的にポインタが宣言されません。
gnumbers_list に対する XDR ルーチンを作成するためのヒントは、上の XDR についての記述から得られます。上の XDR 共用体を実現するためのプリミティブ xdr_pointer()の使用方法を参照してください。
bool_t xdr_gnumbers_node(xdrs, gn) XDR *xdrs; gnumbers_node *gn; { return(xdr_gnumbers(xdrs, &gn->gn_numbers) && xdr_gnumbers_list(xdrs, &gn->gn_next)); } bool_t xdr_gnumbers_list(xdrs, gnp) XDR *xdrs; gnumbers_list *gnp; { return(xdr_pointer(xdrs, gnp, sizeof(struct gnumbers_node), xdr_gnumbers_node)); xdr_pointer}
これらのルーチンを持つリスト上で XDR を使用すると、リスト内のノード数に比例して C スタックが大きくなります。これは再帰呼び出しが原因です。次の例では、互いに再帰呼び出しを行う 2 つのルーチンを 1 つにまとめて、再帰呼び出しが起こらないようにしています。
bool_t xdr_gnumbers_list(xdrs, gnp) XDR *xdrs; gnumbers_list *gnp; { bool_t more_data; gnumbers_list *nextp; for(;;) { more_data = (*gnp != NULL); if (!xdr_bool(xdrs, &more_data)) return(FALSE); if (! more_data) break; if (xdrs->x_op == XDR_FREE) nextp = &(*gnp)->gn_next; if (!xdr_reference(xdrs, gnp, sizeof(struct gnumbers_node), xdr_gnumbers)) return(FALSE); gnp = (xdrs->x_op == XDR_FREE) ? nextp : &(*gnp)->gn_next; } *gnp = NULL; return(TRUE); } |
最初に行うのは、次に続くデータがあるかどうかを調べ、このブール値をシリアライズできるようにします。XDR_DECODE の場合はこの文は不要なことに注意してください。なぜなら、次の文で more_data をデシリアライズするまでブール値はわからないからです。
次の文で XDR 共用体の more_data フィールドに XDR を作成します。次のデータがないことがわかれば、最終ポインタを NULL に設定してリストの終了を示し、処理は終了したので TRUE を返します。XDR_DECODE の場合だけ、ポインタを NULL に設定する意味があることに注意してください。XDR_ENCODE と XDR_FREE の場合は既に NULL になっているからです。
処理内容が XDR_FREE の場合は次に、リスト中の次のポインタアドレスを nextp の値として設定します。この値を設定するのは、リスト内の次の項目のアドレスを検出するために、 gnp を間接参照する必要があるためです。次の文以降では、gnp がポイントする記憶領域は解放され無効となります。しかし、次の文までは、XDR_DECODE 処理内容内に gnp の値が設定されていないため、すべての処理内容にはこの値を設定することはできません。
次にプリミティブ xdr_reference() を使用して、このノードのデータ上で XDR を使用します。xdr_reference() はこれまでに使用した xdr_pointer() に似ていますが、次のデータがあるかどうかを示すブール値を送信しない点が異なります。この情報に対しすでに XDR を使用しているため、xdr_pointer() の代わりに xdr_reference () を使用することができます。
ここで、リストの要素と異なる型の XDR ルーチンを渡していることに注意してください。ルーチン xdr_gnumbers() が渡されますが、実際にリストに入っている各要素の型は gnumbers_node です。xdr_gnumbers_node() を渡さないのは、このルーチンが再帰呼び出しを行うからです。代わりに渡された xdr_gnumbers() は、非再帰部分をすべて XDR 変換します。このような方法がうまくいくのは、各要素の最初の項目が gn_numbers()フィールドの場合だけです。最初のフィールドなので xdr_reference() に渡される両者のアドレスが同じだからです。
最後に gnp の値を更新して、リスト内の次の項目を指すようにします。処理内容が XDR_FREE の場合は保存しておいた値を設定し、それ以外の場合は gnp を間接参照して正しい値を取り出します。再帰呼び出しを使用する方法よりむずかしくなりますが、この方が数多くの手続き呼び出しを使用する場合のオーバヘッドがなくなってパフォーマンスも向上します。もっとも、ほとんどのリストはそれほど大きくはなく、項目数がせいぜい 100 以下のため、その場合は再帰呼び出しを行なっても問題ありません。