ONC+ 開発ガイド

高度な機能

この節では、データ構造体を引き渡す方法を説明します。そのような構造体の 1 つに、リンクリスト (長さは任意) があります。これまでの簡単なサンプルプログラムとは違い、ここでは XDR C ライブラリルーチンと、XDR データ記述言語の両方を使用します。XDR データ記述言語の詳細は、付録 C 「XDR プロトコル仕様」 を参照してください。

リンクリスト

「ポインタのサンプルプログラム」の節では、各個人の総資産と負債に関する C のデータ構造体とそれに対する XDR ルーチンのサンプルプログラムを示しました。例 A-14 では、リンクリストを使用して、ポインタのサンプルプログラムと同じものを作成します。


例 A-14 リンクリストのサンプルプログラム

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()の使用方法を参照してください。


例 A-15 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 のスタックがリスト内のノードの数だけ増大するという問題が起こります。これは再帰呼び出しが原因です。例 A-16 のルーチンは、互いに再帰呼び出しを行う上の 2 つのルーチンを 1 つにまとめて、再帰呼び出しが起こらないようにしたものです。


例 A-16 再帰呼び出しを行わない XDR 変換

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_ENCODEXDR_FREE の場合は既に NULL になっているからです。

処理内容が XDR_FREE の場合は次に、リストの次のポインタアドレスを nextp に保存します。このアドレスはここで保存しておかなければなりません。なぜならば、次の文で gnp をデレファレンスして、リスト内の次の項目のアドレスを取り出しますが、その文を実行すると、gnp が指している記憶領域は解放されてその中のデータが失われるためです。XDR_DECODE の場合は、次の文を実行するまで gnp に値が設定されないため、処理内容によってアドレス保存を行うかどうかを決めています。

次にプリミティブ xdr_reference() を使用して、このノードのデータを XDR 変換します。xdr_reference() はこれまでに使用した xdr_pointer() に似ていますが、次のデータがあるかどうかを示すブール値を送信しない点が異なります。ここでは、その情報を既にXDR 変換してしまっているので、xdr_pointer() は使用しません。ここで、リストの要素と異なる型の XDR ルーチンを渡していることに注意してください。ルーチン xdr_gnumbers() が渡されますが、実際にリストに入っている各要素の型は gnumbers_node() です。xdr_gnumbers_node() を渡さないのは、このルーチンが再帰呼び出しを行うからです。代わりに渡された xdr_gnumbers() は、非再帰部分をすべて XDR 変換します。このような方法がうまくいくのは、各要素の最初の項目が gn_numbers()フィールドの場合だけです。最初のフィールドなので xdr_reference() に渡される両者のアドレスが同じだからです。

最後に gnp の値を更新して、リスト内の次の項目を指すようにします。処理内容が XDR_FREE の場合は保存しておいた値を設定し、それ以外の場合は gnp をデレファレンスして正しい値を取り出します。再帰呼び出しを使用する方法よりむずかしくなりますが、この方が数多くの手続き呼び出しを使用する場合のオーバヘッドがなくなってパフォーマンスも向上します。もっとも、ほとんどのリストはそれほど大きくはないので (項目数がせいぜい 100 以下)、その場合は再帰呼び出しを行なっても問題ありません。