メインフレームの「見えない領域」を制する:PL/I AUTOMATIC変数のスタックと再帰の深淵
若手のエンジニアから、「PL/Iのバッチ処理で、再帰呼び出しを使ったら突然ABEND U4038(あるいはストレージ不足系)が発生した」という相談を受けることがよくある。
現代のシステムでは仮想記憶が潤沢になったとはいえ、メインフレームの歴史的なアーキテクチャ、特にスタック領域の管理は、今なお我々エンジニアが避けては通れない「聖域」だ。今日は、PL/Iの`AUTOMATIC`ストレージが裏側でどう振る舞っているのか、そして再帰呼び出しとどう向き合うべきか、現場の視点で深掘りしていこう。
—
AUTOMATIC変数の「寿命」とスタックの仕組み
PL/Iにおいて、デフォルトで宣言される変数(`DECLARE X FIXED BIN(31);`など)は、暗黙的に`AUTOMATIC`として扱われる。これは、その`PROCEDURE`が呼び出された瞬間にスタック上にメモリが確保され、`END`で戻る時に解放される、という極めて効率的かつ安全なライフサイクルを持っている。
しかし、ここで忘れてはならないのが、「スタック領域は無限ではない」という現実だ。
再帰呼び出し時の挙動
再帰呼び出し(`RECURSIVE`オプションをつけたプロシージャ)を行うと、呼び出しのたびに新しいスタックフレームが積み上がる。例えば、数百メガバイトの巨大な構造体(`DECLARE`で定義した配列など)を`AUTOMATIC`で確保したまま再帰すると、スタック領域などあっという間に枯渇する。
実践コード:再帰によるスタック消費の可視化
以下に、再帰処理の典型的なパターンと、実務でよくある「うっかり」を防ぐための記述例を示す。
1
/ 再帰による階乗計算のサンプル /
/ 重要なのは、スタックを圧迫しないような実装を心がけること /
FACTORIAL_PROC: PACKAGE;
/ メイン処理 /
MAIN_PROC: PROCEDURE OPTIONS(MAIN);
DCL RESULT FIXED BIN(31);
DCL CALCULATE_FACT ENTRY(FIXED BIN(31)) RETURNS(FIXED BIN(31));
RESULT = CALCULATE_FACT(10);
PUT SKIP LIST(‘CALCULATION RESULT:’, RESULT);
END MAIN_PROC;
/ 再帰プロシージャ:RECURSIVE指定を忘れずに /
CALCULATE_FACT: PROCEDURE(N) RECURSIVE RETURNS(FIXED BIN(31));
DCL N FIXED BIN(31);
DCL TEMP_VAL FIXED BIN(31); / AUTOMATIC変数:呼び出しごとに生成される /
/ 再帰の深さを制御するための安全装置 /
IF N <= 1 THEN RETURN(1);
TEMP_VAL = N CALCULATE_FACT(N - 1);
RETURN(TEMP_VAL);
END CALCULATE_FACT;
END FACTORIAL_PROC;
現場で「死なない」ための3つの知恵
このプログラムを改修する際、あるいは大規模なマイグレーションを行う際、以下の点に注意してほしい。
1. 大容量データは AUTOMATIC を避ける
数MBを超えるような作業用配列を`AUTOMATIC`で宣言すると、並行して動いている他の処理に影響を与え、スタックオーバーフローを誘発する。どうしても必要な場合は、`STATIC`(静的)あるいは`CONTROLLED`(明示的なALLOCATE/FREE)を使用することを検討せよ。
2. ONユニットによる異常系監視
スタック枯渇のような実行時エラーは、予期せぬ場所で発生する。`ON STORAGE`ユニットを活用し、異常発生時にダンプだけでなく、現在のスタックの深さをログに出力するような防壁を仕込んでおくのがベテランの流儀だ。
1
ON STORAGE BEGIN;
PUT SKIP LIST(‘— STORAGE EXHAUSTED: STACK OVERFLOW LIKELY —‘);
/ 呼び出し元の情報をトレースするルーチンを呼ぶ /
CALL DUMP_STACK_TRACE;
SIGNAL FINISH;
END;
3. BUILTIN関数の活用
スタックの状況を確認したい場合、`ADDR`や`STORAGE`といった組み込み関数を使って、デバッグ時には現在のスタックポインタ付近の動向を監視するコードを挿入するのも手だ。ただし、これらはあくまで開発環境用として記述し、本番投入時はコメントアウトするか、プリプロセッサ(`%IF`)で制御すること。
—
結びとして
再帰呼び出しは、論理的には非常にスマートで美しいコードを実現できる。しかし、メインフレームの限られたリソース環境下では、それは「諸刃の剣」だ。
「この変数、本当にスタック上に置く必要があるか?」
「再帰の深さは最大でどれくらいになるか?」
設計段階でこれらを見積もる能力こそが、我々PL/Iエンジニアの腕の見せ所だ。バッチの夜間運用で冷や汗をかかないために、スタックの向こう側にあるメモリの気配を、常に意識してコーディングしてほしい。
何か疑問があれば、いつでも聞いてくれ。この現場のコードは、我々が守るのだから。
