この節では、データ構造体を引き渡す方法を説明します。そのような構造体の 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 以下のため、その場合は再帰呼び出しを行なっても問題ありません。