動的配列がRPGにやって来た
約12か月前、7.4のリリースが発表された際に、「 7.4で提供されるRPGの新機能 」という記事で、7.4の新機能のうち、7.3でも利用可能なものについて紹介しました。その際、7.4でのみ利用可能な機能については、今後の記事で取り上げると述べました。7.4を利用するショップもかなりの数になっているようであり、その時が来たようです。
動的配列は、「この配列は、どれくらいのサイズで作成する必要があるのでしょうか」という、長年のプログラマーの問いに対する答えです。私の経験からすると、配列をどれくらいのサイズで作成するかは、実際には重要ではなく、後のある時点で、非常に小さくなります。ようやく、この問いに対する答えが見つかりました。
それでは、それらの配列はどのように定義するのでしょうか。簡単です。DIMの仕様に新たなキーワード、*AUTOまたは*VARのいずれかを追加するだけです。たとえば、次のようにします。
dcl-s autoArray Char(10) Dim(*Auto: 50);
dcl-s varyArray Char(10) Dim(*Var: 500);
では、それら2つのタイプの違いは何でしょうか。自動(*Auto)配列は、必要に応じて自動的にサイズが増大します。可変(*Var)配列は、サイズが変動し、プログラマーの制御下で増減させることができます。このテーマに関する記事のうち、1つ目となる今記事では、*Autoを中心に見て行きます。今後の記事では、*Var配列について取り上げ、この新たなサポートのいくつかの制限事項を処理する方法についても説明するつもりです。
ただし、先に進む前に、少し時間を取って、後で重要になるであろう、いくつかの用語を確認しておこうと思います。従来のRPG配列を見てみましょう。
dcl-s normalArray Char(10) Dim(50);
ご覧の通り、この配列は、 最大キャパシティー が50要素で定義されています。いくつのエントリーを取り込んできたかは関係なく、この配列の 現行キャパシティー は50要素とみなされます。SORTAおよび%LOOKUP命令のスコープを管理するのは、この 現行キャパシティー値 です。また、配列の 記憶域の割り振り も、50要素に設定される点に注意してください。
それでは、これらの用語の定義を念頭に置いて、この新たなサポートがどのように機能するかについて見てみましょう。
可変長配列の使用法
*Autoまたは*Varで定義される配列は、 現行キャパシティー が0で始まる点が従来の配列とは異なります。DIMの2つ目のパラメーターは、配列の 最大キャパシティー を指定します。配列は増大することはできますが、このサイズを超えることはできないため、暴走コードが無限に続行されるのを防止するサニティ チェックとなります。
例を見てみましょう。
- (A)で定義されている独立型配列、autoArrayは、現行キャパシティーが0に設定されて開始されます。
- (B)のコードがその値より大きい指標値(1)を使用すると、この新たな要素を収容するために、現行キャパシティーは1に上がります。
- (C)では、10番目の位置に値を配置しています。ご推察の通り、その結果として、RPGは現行キャパシティーを10に増やします。中間の要素である2~9は、配列のデータのタイプに応じたデフォルト値に初期化されます。このケースではブランクとなります。
- (D)の行は、キャパシティーに何も影響を与えません。9は10より小さいからです。したがって、値が9番目の位置に配置されるだけです。
- 最後の(E)では、配列の上限値として50という「サニティ チェック」値を選択した効果が確認できます。要求された拡張が、許容される最大キャパシティーを超えるため、実行時エラーが発生します。
(A) dcl-s autoArray Char(10) Dim(*Auto: 50);
(B) autoArray(1) = 'One';
(C) autoArray(10) = 'Ten';
(D) autoArray(9) = 'Nine';
Index = 90;
(E) autoArray(index) = 'Ninety'; // This will go BOOM!
さらなる素晴らしいメリット
配列が使用されている多くのケースでは、より多くのアイテムが配列に追加されるにつれて、コードは指標値を増分させます。この新たなスタイルの配列とともに、そのようなシナリオをよりコーディングしやすくする新たな指標オプションが加わりました。以下のコードをご覧になると、新たな *Next 指標値を使用することで、どのようにコーディング作業を単純化させることができるかがお分かりいただけると思います。
(F)で始まるコードは、今日の一般的な手法である、配列に新たな要素を追加する前にカウンターを増分させるやり方の例です。(G)では、指標として*Nextを指定することにより、これがどれほど単純化されるかが分かると思います。これにより、コンパイラーは現行キャパシティーの値を増分し、その新たな指標を使用してデータを格納することになります。たとえば、現行キャパシティーが15要素である場合、*Nextを使用することにより、キャパシティーが16に上げられ、新たなアイテムは指標16に格納されることになります。
(F) // Add new element to array
count += 1;
oldArray(count) = 'New Value';
(G) // *Auto version
autoArray(*Next) = 'New Value'
*NextはautoArrayの現行キャパシティーを拡大しますが、最大キャパシティーは50に固定されたままであることに注意してください。*Nextを使用して(さらに言えば、指標で直接50を指定することによって)50番目の要素にデータが配置されたら、*Next要素を使用して何かを行おうとしても、すべて失敗します。最大限が50として定義されている配列で要素51を作成しようとしたことになるからです。
それはそれでよいとして、経過を追跡するコードがない場合、配列の現行キャパシティーがいくつなのかは、どのようにして判定できるのでしょうか。
この問いに対する答えは、実は、2つあります。1つ目の答えは、そうする必要はないかもしれないというものです。というのは、RPGが自動的にSORTAや%LOOKUPなどの配列命令を現行の配列キャパシティーに制限するため、%SUBARRを使用してそのような命令をアクティブな要素に制限する必要はないからです。
2つ目の答えは、実数値を知っている必要があるケースでは、古くからの友人である%ELEMに提供してもらえばよい、というものです。
記憶域の割り振り
この配列と従来のRPG配列サポートの違いの1つは、配列が「必要に応じて増大する」ためには、配列によって使用されるメモリーは動的でなければならない、という点だと思われたかもしれません。これはその通りであり、また、このことは、状況によって、RPGは配列全体を別のメモリー ロケーションに移動する必要が生じることがあるということも意味しています。しかし、心配無用です。RPGは、ちょうど%ALLOCや%REALLOCに関連付けられた動的メモリーを保持するのと同じように、配列の内容を保持することを保証します。
qsortなどのルーチンを使用して配列の並べ替えを行う場合は、そのような移動の結果として配列のアドレスが変更されることになる点に注意する必要があります。そのため、%ADDR関数を使用して配列のアドレスを取得する場合に、配列サイズが変更されている可能性があるときは、使用する前に必ずポインター値をリフレッシュしておく必要があります。
また、こうした繰り返し行われるメモリーの再割り振りによって、パフォーマンス オーバーヘッドが生じるのではないかと思われるかもしれません。これは理論的には問題であるかもしれませんが、実際的には、問題になることはありません。1つには、RPGがオペレーティング システムに対して動的メモリーを供給するように要求すると、オペレーティング システムは通常、要求されたより多くの量を割り振るからです。システムは、そうすることによってメモリーの断片化を防ぎます。この1文で好奇心が刺激された方には、そのトピックについての優れた説明が記載されていますので、「 en.wikipedia.org/wiki/Fragmentation_(computing)」をご覧になることをお勧めします。
そのため、RPGが(たとえば)100バイトを要求した場合には、システムは512バイトを割り振ってくるかもしれません。そうではあっても、そうした頻繁な再割り振りが必要となる可能性があることが心配な場合は、この先を読み進めて、どのようにすれば、割り振られる記憶域の量を管理して頻繁な再割り振りの機会を少なくすることができるかについて確認してください。
以下がその方法です。現行のメモリー割り振りによって、いくつの配列エントリーを収容できるかを確認するには、やはり、古くからの友人である%ELEMの手を借りることができます。実際、いくつの要素が必要となるかもしれないか、あらかじめ分かっている場合は、その値を利用してメモリー割り振りを設定することもできます。以下のコード サンプルで、これがどのように行われるかを確認できます。
dcl-s autoArray2 Char(5) Dim(*Auto: 10000);
(H) Dsply ('Initial size = ' + %Char(%Elem(autoArray2)));
(I) Dsply ('Allocation = ' + %Char(%Elem(autoArray2 : *Alloc)) );
(J) %Elem(autoArray2 : *Alloc) = 150;
(K) Dsply ('Allocation = ' + %Char(%Elem(autoArray2 : *Alloc)) );
このコードを実行すると(ご使用のシステムが私のシステムと同じように動作することが前提です)、(I)のDsplyは、(H)で現行キャパシティーが0であることが示されていたとしても、100の要素を収容するのに十分な記憶域が割り振られていることを示します。
(J)では、150の要素に対して十分な量の記憶域の割り振りが要求されます。しかし、(K)にある次のDsplyは、250に対応する記憶域が実際に割り振られたことを示します。私の実験で分かったのは、RPGは、要求されたよりも100多い要素に対応するスペースを割り振るようだということです。ただし、これが常に正しいかどうかIBMに確認することはできていません。また、現時点でそれが正しいとしても、今後、いつでも変わる可能性があるため、あまり頼りにし過ぎないようにする方がよいと思います。
次回予告
次回の記事では、これらの新たな動的配列の*VARバージョンがどのように使用されるかについて見て行き、この初期サポートでのいくつかの制限を処理する方法について説明する予定です。