【テクニカル・上級編】RECURSIVE属性による再帰呼び出しの制御 – PL/Iの基本構文とデータ制御実践ガイド

再帰の深淵:PL/IにおけるRECURSIVE属性と「スタック」という名の呪縛

基幹システムの設計において、再帰処理(Recursive)は諸刃の剣だ。特にPL/Iにおいては、単に `RECURSIVE` キーワードを付与すれば済むという甘い話ではない。スタック管理、ストレージの生存期間、そして何より「汎用機特有のメモリ制約」を理解しなければ、本番環境で突如として発生する `S0C4` や `S80A` アベンドの餌食となる。

今日は、PL/Iにおける再帰の制御と、マイグレーションを見据えた実務的なスタック管理について、現場の知見を共有しよう。

RECURSIVE属性の真実

PL/Iで再帰を行うには、プロシージャの定義に `RECURSIVE` 属性が必須だ。これを怠れば、コンパイラは呼び出しのたびに自動変数を再配置する準備をせず、前回呼び出し時の値を上書きしてしまう。

/i
/ 再帰プロシージャの定義例 /
CALC_HIERARCHY: PROCEDURE(P_LEVEL) RECURSIVE;
DCL P_LEVEL FIXED BIN(15);
DCL W_WORK_AREA CHAR(4096) AUTO; / 毎回4KBのスタックを消費する /

/ ここでポインタを用いた動的領域操作を行う場合、
解放漏れがスタック枯渇より先にヒープを食いつぶす /
IF P_LEVEL > 0 THEN DO;
CALL CALC_HIERARCHY(P_LEVEL – 1);
END;
END CALC_HIERARCHY;

ここで重要なのは、`AUTO` 変数がスタックフレームを消費するという点だ。Javaのヒープに慣れた世代には理解しがたいかもしれないが、汎用機ではスタック領域(主に `DSA: Dynamic Storage Area`)は非常にタイトに設計されている。何百もの深さを持つ再帰を無策で実装すれば、あっという間に `STORAGE SHORTAGE` に直面する。

スタックオーバーフローと動的メモリ管理の最適解

もし、再帰処理の中で大きなデータ構造を扱う必要があるならば、`AUTO` 変数でスタックを汚してはいけない。`BASED` 変数と `POINTER` を駆使し、明示的に `ALLOCATE` と `FREE` を制御すべきだ。

/i
DCL T_DATA_PTR POINTER;
DCL T_DATA CHAR(1024) BASED(T_DATA_PTR);

/ 再帰開始前にヒープから領域を取得 /
ALLOCATE T_DATA;

/ 再帰処理… /

/ 処理終了後に即座に解放。これを怠るとメモリリークの地獄を見る /
FREE T_DATA;

特にDB2やCICS環境下では、再帰の深さが予測不能な場合、スタックの枯渇がそのままオンライン端末の応答停止や、バッチの異常終了に直結する。マイグレーションを視野に入れるなら、再帰を「ループ構造(スタックを自前で保持する実装)」へ書き換えるのが、最も安全で、かつJava/C#への移行時に苦労しない設計だ。

現場で遭遇する「罠」:パックデシマルの符号反転とアベンド

再帰処理で計算結果を次々と引き渡す際、最も恐ろしいのは内部データ形式の不整合だ。特に、`DECIMAL FIXED`(パックデシマル)を扱う際、コンパイラの最適化レベル(`OPTIMIZE(2)`以上)によっては、レジスタへのロード時に符号ビットの解釈が微妙に変わるケースがある。

もしダンプ解析で `S0C7`(データ例外)が多発するなら、再帰の入り口で必ず `FIXEDBIN` へのキャストを行い、計算の整合性を担保してほしい。また、`SQLCA` を介したDB2コールが含まれる場合、再帰内でカーソルをクローズし忘れると、`SQLCODE -501`(カーソル未定義)の嵐に巻き込まれる。CICSであれば、`GETMAIN` した領域の解放を再帰の終了判定時に確実に行うための「例外処理(`ON CONDITION`)」の設計が必須となる。

アーキテクトとしてのアドバイス:移行を見据えて

PL/Iの再帰をJavaに移行する場合、JVMのスタックサイズ設定(`-Xss`)が最初の障壁になる。しかし、真の地獄はそこではない。PL/Iの「柔軟すぎるポインタ操作」を、Javaの参照型にどうマッピングするかだ。

1. 深い再帰は「キュー」や「スタッククラス」へ置換せよ。
2. `BASED` 変数は「DTO(Data Transfer Object)」として定義し直せ。
3. `ON CONDITION` 等のPL/I特有の例外処理は、Javaの `try-catch-finally` で細かく分解せよ。

再帰処理のコードを読んでいるとき、私はその設計者の「メモリに対する敬意」を見ている。限られたリソースの中で、いかに効率よく計算を完結させるか。それが、メインフレームプログラマの矜持だ。

もし今、あなたの抱えるPL/Iプログラムがアベンドを繰り返しているなら、まずはスタックの深さと、`ALLOCATE` された領域の解放漏れを疑え。コードは嘘をつかない。コンパイラの吐き出すリストとダンプを紐解けば、そこには必ず答えがある。

コメント

タイトルとURLをコピーしました