【実務・中級編】STORAGE属性(AUTOMATIC vs STATIC)のメモリ配置 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近入った若手が「PL/Iって変数名に予約語が使えないから面倒くさいですね」なんて言っていたのを聞いてね、思わず苦笑いしてしまったんだ。

ちょっと待て、と。君はPL/Iの本質をまだ分かっていない。
実は、PL/Iの最大の言語的特徴であり、かつ現代のC言語やJava脳のエンジニアが最もハマりやすい罠、そして最大の武器がそこにある。PL/Iには、いわゆる「狭義の予約語(Reserved Words)」というものが存在しない。キーワードはすべて「文脈依存の予約語(Contextual Keywords)」なのだ。つまり、変数名に `IF` や `READ` と書いたって、コンパイラは前後の文脈から「あ、これは変数だな」「ここは制御文だな」と賢く判断して解釈してくれる。まあ、そんな変態的なコーディングをする奴がいたら現場の総スカンを食らうがね。

さて、前置きはこれくらいにしておこう。今回は、基幹システムのバッチ処理やオンライン応答プログラムのメモリ管理において、避けて通れない「STORAGE属性(AUTOMATIC vs STATIC)のメモリ配置と再帰呼び出し時の挙動」について、現場のシニアの視点から徹底的に叩き込んでやろう。

メインフレームの限られたリソース(Z/OSのアドレス空間)を極限まで効率化し、夜間バッチの処理時間を1秒でも削るための必須知識だ。心して読むように。

1. AUTOMATIC と STATIC:メモリ配置の根本的な違い

PL/Iのデータ宣言において、記憶クラス(Storage Class)の指定はプログラムのライフサイクルとメモリ効率を決定づける最も重要な要素だ。デフォルトでは、プロシージャ(サブルーチン)内で宣言された変数は `AUTOMATIC` になる。

AUTOMATIC(自動変数)

  • メモリ配置場所: タスクごとのスタック領域(Stack Storage)。
  • ライフサイクル: プロシージャ(またはブロック)が呼び出された時に動的に割り当てられ、ブロックを抜け出す(`RETURN` や `END`)と自動的に解放される。
  • 初期化: ブロックに入るたびに初期化されるため、前回の実行時の値は保持されない(明示的な `INIT` 属性がない限り、値は不定)。

STATIC(静的変数)

  • メモリ配置場所: 静的領域(Static Storage / Load Module内またはGETMAINされた永続領域)。
  • ライフサイクル: プログラム(ロードモジュール)がロードされてから、終了するまで常にメモリ上に常駐する。
  • 初期化: プログラムのロード時に一度だけ初期化される。2回目以降のプロシージャ呼び出しでも、前回の処理終了時の値を保持し続ける

> ベテランからの警告:
> オンラインプログラム(CICSなど)や、リエントラント(再入可能)であることが必須のバッチプログラムで、うっかりワークエリア的な変数を `STATIC` で定義してしまったらどうなるか。次のトランザクションや別タスクの実行時に前回のゴミデータが残り、「原因不明のデータイメージ化け」「ランダム異常終了(S0C4やASRAなど)」という悪夢を引き起こす。モダナイゼーションやマイグレーションの際、C言語の `static` のノリで安易に `STATIC` をつけるプログラマが後を絶たないが、メインフレームでは致命傷になり得るので細心の注意を払うこと。

2. 再帰呼び出し(RECURSIVE)と AUTOMATIC の真価

PL/Iでマスタファイルの階層構造展開や、複雑なBOM(部品表)の展開などを実装する際、`RECURSIVE` オプションが猛威を振るう。この時、`AUTOMATIC` 変数がどのようにスタックに積み上げられるかを知ることが、バグのない堅牢なコードを書くカギだ。

再帰呼び出しが行われるたびに、そのプロシージャ内で宣言されている `AUTOMATIC` 変数は、呼び出し階層ごとに新しいスタック領域へ個別に割り当てられる。つまり、自分自身のインスタンスが何重にネストしても、他の階層の変数を上書きしてしまう心配がない。

一方で、もしそこに `STATIC` 変数が混ざっていたらどうなるか? すべての再帰呼び出しが同一のメモリ領域を共有するため、値が書き換わりまくり、再帰のツリー構造が崩壊する。再帰プロシージャ内での作業変数は、原則としてすべて `AUTOMATIC`(あるいは明示的な無指定)でなければならない。

3. 実践:VSAM入出力と再帰的データ構造を扱うPL/Iサンプルコード

百聞は一見に如かず。実際のメインフレーム開発現場を想定した実用的なPL/Iソースコードを見てみよう。
このコードは、VSAM(KSDS)から組織コードの階層マスタを読み込み、再帰処理を用いて下位組織を辿りながらログ出力を行うバッチプログラムの抜粋だ。`AUTOMATIC` と `STATIC` の使い分け、そして実務で必須の `BUILTIN` 関数(`LENGTH`, `INDEX` など)や `ON` ユニットによる例外処理を含めている。

1
/ /
/ PROGRAM-ID: ORGURG01 /
/ FUNCTION: 組織階層マスタ再帰展開バッチ処理 /
/ /
ORGURG01: PROC OPTIONS(MAIN);

/ 宣言部 /
DCL VSAM_FILE FILE RECORD SEQUENTIAL INPUT
ENVIRONMENT(BUFND(5) BUFNI(2));

DCL 1 ORG_REC,
5 ORG_CODE CHAR(6), / 組織コード /
5 ORG_PARENT CHAR(6), / 親組織コード /
5 ORG_NAME CHAR(40); / 組織名称 /

DCL EOF_FLG CHAR(1) STATIC INIT(‘0’); / 静的フラグ /
DCL ERROR_COUNT FIXED BIN(31) STATIC INIT(0); / 静的カウンタ /

/ ファイル終了(EOF)およびVSAMエラー時のONユニット制御 /
ON ENDFILE(VSAM_FILE) EOF_FLG = ‘1’;

ON ERROR
BEGIN;
PUT SKIP LIST(‘ CRITICAL ERROR OCCURRED IN BATCH ‘);
ERROR_COUNT = ERROR_COUNT + 1;
GOTO JOB_ABEND;
END;

/ 処理開始 /
PUT SKIP LIST(‘=== 組織階層展開処理を開始します ===’);
OPEN FILE(VSAM_FILE);

/ ルート組織(親がスペースの組織)を起点に処理を回す /
/ ここでは簡易的に全件スキャンからの再帰呼び出しをシミュレート /
DO WHILE (EOF_FLG = ‘0’);
READ FILE(VSAM_FILE) INTO(ORG_REC);
IF EOF_FLG = ‘1’ THEN LEAVE;

/ 組織コードの有効性チェック(BUILTIN関数活用) /
IF VERIFY(ORG_REC.ORG_CODE, ‘0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ’) ^= 0 THEN
DO;
PUT SKIP EDIT(‘INVALID ORG CODE FOUND: ‘, ORG_REC.ORG_CODE) (A, A);
ITERATE;
END;

/ 頂点ノードからの再帰展開プロシージャ呼出し /
IF ORG_REC.ORG_PARENT = ‘ ‘ THEN
CALL EXPAND_CHILD_ORG(ORG_REC.ORG_CODE, 1);
END;

CLOSE FILE(VSAM_FILE);
PUT SKIP EDIT(‘NORMAL END. ERROR COUNT: ‘, ERROR_COUNT) (A, F(5));
RETURN;

JOB_ABEND:
/ 異常終了処理 /
IF ^((FILE(VSAM_FILE)) = CLOSED) THEN CLOSE FILE(VSAM_FILE);
CALL PLIRETC(16); / 異常終了コード設定 /
RETURN;

/ /
/ 子組織再帰展開サブプロシージャ /
/ /
EXPAND_CHILD_ORG: PROC(P_PARENT_CODE, P_LEVEL) RECURSIVE;

DCL P_PARENT_CODE CHAR(6) PARAMETER;
DCL P_LEVEL FIXED BIN(15) PARAMETER;

/ 【重要】スタックに配置される自動変数(再帰ごとの独立領域) /
DCL W_INDENT CHAR(20) AUTOMATIC;
DCL W_WORK_NAME CHAR(40) AUTOMATIC;
DCL I FIXED BIN(15) AUTOMATIC;

/ インデント文字列の動的生成(BUILTIN関数: REPEAT, TRIM) /
W_INDENT = REPEAT(‘ ‘, P_LEVEL 2);
W_WORK_NAME = TRIM(P_PARENT_CODE);

PUT SKIP EDIT(W_INDENT, ‘LEVEL:’, P_LEVEL, ‘ ORG:’, W_WORK_NAME) (A, A, F(3), A);

/ 本来はここで子孫レコードを検索・ループするロジックが入る /
/ 例として深度が3に達したら再帰を抜けるブレークを記述 /
IF P_LEVEL < 3 THEN DO; / 次の階層へ再帰呼び出し(P_LEVELをインクリメント) / / AUTOMATIC変数の値は階層ごとに完全に独立して保持される / CALL EXPAND_CHILD_ORG('SUB001', P_LEVEL + 1); END; RETURN; END EXPAND_CHILD_ORG; END ORGURG01; ---

4. 現場のシニアから送るデバッグの極意とコーディング規約

このコードを見て、「なぜ `EOF_FLG` や `ERROR_COUNT` は `STATIC` なのに、サブプロシージャ内の `W_INDENT` や `I` は `AUTOMATIC` なのか?」とピンと来たなら、君のアーキテクトとしての勘所はかなり良い。

1. バッチ全体のステータス管理は STATIC、プロシージャ内のローカルワークは AUTOMATIC の原則を徹底せよ。
グローバルに共有すべきカウンターやフラグを除き、原則として変数は無指定(AUTOMATIC)にするのがPL/Iコーディングの王道だ。余計な `STATIC` を乱用すると、マルチタスク環境や再帰呼び出し時にメモリ破壊の温床となる。
2. ONユニットとスタックの関係に注意せよ。
`ON ERROR` や `ONENDFILE` などの割り込み処理(ONユニット)は、発生時のコンテキストを引き継ぐ。例外発生時にスタック上の `AUTOMATIC` 変数を参照する場合、ブロックのスコープ外に飛び出していると参照不能になるケースがあるため、エラーログに出力したい重要情報は `STATIC` 領域の共通エリアに退避させるなどの防衛的プログラミングが求められる。
3. マイグレーション時のコンパイラオプション(ENTERPRISE PL/I)
近年のEnterprise PL/Iコンパイラでは、デフォルトの記憶クラスや最適化レベル(`OPTIMIZE`)によって、自動変数のレジスタ割付やスタックの振る舞いが変わることがある。テスト環境と本番環境で挙動が微妙に違うといった不可解な現象に遭遇したら、まずはコンパイラリストの `MAP` および `CROSS-REFERENCE` 出力を睨み、意図したメモリ配置になっているかを必ず自分の目で確認するんだ。

基幹システムの命であるデータとメモリの整合性。これを守り抜くのが、我々メインフレームエンジニアのプライドだ。今日の解説をしっかりと腹に落とし、明日の設計書レビューやコーディングに活かしてほしい。質問があればいつでも自分の席に来るといい。

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