おい、最近の若手はオープン系の言語ばかり触ってきたせいか、メインフレームのメモリ管理、特にPL/Iのストレージクラス(属性)の挙動を軽く見がちだな。
「変数なんて、どこで宣言しても同じだろう」なんて油断していると、夜間バッチのテスト工程で突如としてデータが化けたり、再入可能性(Reentrancy)の違反でCICSのトランザクションがアベンド(異常終了)したりして、血の気が引く思いをすることになる。
今回は、PL/Iにおける `AUTOMATIC`(自動)属性 と `STATIC`(静的)属性 のメモリ配置と生存期間(Lifetime)の違いについて、実務に直結する視点から徹底的に叩き込んでやる。これさえ押さえておけば、メモリリークや意図しない値の持ち越しといったレガシー特有のハマりどころを華麗に回避できるようになるはずだ。
—
1. 現場のエンジニアが知るべき `AUTOMATIC` と `STATIC` の本質
PL/Iの優れている(そして時に恐ろしい)点は、変数のメモリ割り当てをプログラマが細かく制御できることだ。特に意識すべきなのが、デフォルトの挙動の違いだ。
AUTOMATIC属性:スタックの住人
- メモリ配置: タスク(あるいはブロック)ごとのスタック領域に動的に割り当てられる。
- 生存期間: そのブロック(プロシージャやBEGINブロック)に入った時に領域が確保され、抜け出た瞬間に消滅する。
- 初期化: ブロックに入るたびに、初期値は保証されない(毎回ゴミが入っているか、あるいは前回消えた残骸が残っている可能性さえある)。そのため、明示的な初期化(`INITIAL`属性など)がない限り、値が不定であることを前提にコードを書かなければならない。
- デフォルト: プロシージャ内で明示的に属性を指定しない場合、スカラー変数の多くは暗黙的に `AUTOMATIC` とみなされる(※コンパイラオプションやコンテキストによるが、基本のキとして覚えておけ)。
STATIC属性:ロードモジュールの住人
- メモリ配置: ロードモジュールの一部(あるいは常駐セクション)として静的にメモリが確保される。
- 生存期間: プログラムがロードされてから、終了(エンド)するまでずっと同じアドレスに居座り続ける。
- 初期化: プログラムのロード時に一度だけ初期化される。次回以降、ブロックに再入しても初期化はされない。つまり、前回の処理で書き換えた値がそのまま保持される。
- 用途: ルックアップテーブル、累積カウンター、あるいは前回値を保持する必要がある制御フラグなどに用いる。ただし、オンライン(CICSなど)でこれをやるとマルチスレッド環境で致命的なバグ(非再入可能コードの悲劇)を引き起こすので細心の注意が必要だ。
—
2. VSAMレコード処理とONユニットが絡む実務コード例
百聞は一見にしかずだ。実際の夜間バッチ改修でよく見かける、VSAM(KSDS)のマスターファイルを順次読み込み、コントロールブレイク制御を行いながら、エラー時は `ONU (ON-unit)` でトラップする実用的なPL/Iプログラムを見てみよう。
このコードの中に、`STATIC` によるカウンター保持と、`AUTOMATIC` によるレコードワークエリアの管理を綺麗に同居させている。隅々までコメントを読み込んでくれ。
1
/ ================================================================= /
/ PROGRAM-ID: MSTRRPT1 /
/ REMARKS : 顧客マスタ(VSAM/KSDS)を読み込み、種別ごとに集計するバッチ /
/ ================================================================= /
MSTRRPT1: PROC OPTIONS(MAIN);
/ — 1. 外部ファイル(VSAM)の定義 — /
DCL CUSTMSTR FILE RECORD SEQUENTIAL INPUT
ENVIRONMENT(VSAM);
/ — 2. ワークエリアおよび構造体の定義 — /
/ レコードレイアウト(スタック上に毎回展開されるAUTOMATICが適切) /
DCL 1 CUST_REC,
5 CUST_ID CHAR(8),
5 CUST_TYPE CHAR(2),
5 CUST_NAME CHAR(30),
5 CUST_BALANCE FIXED DEC(11,2);
/ — 3. STATIC属性の変数群(ロードモジュール内に常駐) — /
/ 読み込み件数を保持するため、ブロックを抜けても値を維持する必要がある /
DCL W_READ_COUNT FIXED BIN(31) STATIC INIT(0);
DCL W_ERR_COUNT FIXED BIN(31) STATIC INIT(0);
DCL W_TOTAL_BAL FIXED DEC(15,2) STATIC INIT(0);
/ — 4. 制御フラグおよびループ制御変数 — /
DCL EOF_FLG CHAR(1) AUTOMATIC INIT(‘0’);
/ 組み込み関数(BUILTIN)の宣言 /
DCL (SUBSTR, TRIM) BUILTIN;
/ — 5. ONユニット(例外処理)の定義 — /
/ 入力ファイルでENDFILE条件が発生したときのハンドリング /
ON ENDFILE(CUSTMSTR)
BEGIN;
EOF_FLG = ‘1’;
END;
/ 入出力エラー(KEY/TRANSMITなど)の捕捉 /
ON ERROR
BEGIN;
DISPLAY(‘ ERROR OCCURRED IN CUSTMSTR PROCESSING ‘);
W_ERR_COUNT = W_ERR_COUNT + 1;
/ 強制的に異常終了コードを返すか、処理を続行するかの判断 /
GOTO ERR_ROUTINE;
END;
/ — 6. メイン処理ロジック — /
DISPLAY(‘MSTRRPT1: 処理を開始します。’);
/ VSAMファイルのオープン /
OPEN FILE(CUSTMSTR);
/ 初期レコードの読み込み /
READ FILE(CUSTMSTR) INTO(CUST_REC);
/ メインループ /
DO WHILE(EOF_FLG = ‘0’);
/ 読み込み成功時の件数カウント(STATIC変数を利用) /
W_READ_COUNT = W_READ_COUNT + 1;
/ 残高の累積(STATIC変数を利用) /
W_TOTAL_BAL = W_TOTAL_BAL + CUST_BALANCE;
/ 各種ビジネスロジック(例:特定の顧客種別をチェック) /
IF CUST_TYPE = ’01’ THEN DO;
/ プレミアム顧客向けの処理(省略) /
END;
/ 次レコードの読み込み /
READ FILE(CUSTMSTR) INTO(CUST_REC);
END;
/ 正常終了時のサマリー出力 /
DISPLAY(‘— 処理結果サマリー —‘);
DISPLAY(‘総読み込み件数: ‘ || TRIM(W_READ_COUNT));
DISPLAY(‘エラー発生件数: ‘ || TRIM(W_ERR_COUNT));
DISPLAY(‘残高トータル : ‘ || TRIM(W_TOTAL_BAL));
GOTO NORMAL_EXIT;
ERR_ROUTINE:
DISPLAY(‘異常終了ルートを通りました。エラー件数=’ || TRIM(W_ERR_COUNT));
CLOSE FILE(CUSTMSTR);
SIGNAL ERROR; / ジョブを異常終了させる /
NORMAL_EXIT:
/ ファイルのクローズ /
CLOSE FILE(CUSTMSTR);
DISPLAY(‘MSTRRPT1: 正常終了しました。’);
RETURN;
END MSTRRPT1;
—
ジジ臭い話に聞こえるかもしれないが、このコードの `W_READ_COUNT` や `W_TOTAL_BAL` に うっかり `AUTOMATIC` を指定してしまったらどうなるか、想像がつくだろうか?
もしこれらを `AUTOMATIC` にしてしまうと、プロシージャを抜けるたびにメモリが解放され、次に呼び出された時には値が綺麗さっぱり消え去る(あるいはゴミになる)。結果として、集計値が常にデタラメな数値になり、夜間バッチの金額照合で監査法人から鋭いツッコミを受ける大惨事につながるのだ。
逆に、オンライン(CICS)環境で動作するプログラムの中で、ビジネスロジックを保持するワークエリアに `STATIC` を安易に使うと、別のユーザーがトランザクションを叩いたときに前任者のデータが残っている(汚染されている)という、セキュリティ上も実務上も最悪なバグ(マルチタスク環境でのデータ競合)を引き起こす。
—
3. ベテランからの実践的なアドバイスとデバッグのコツ
1. デフォルトに頼るな、明示的に書け:
PL/Iは文脈によって変数のデフォルト属性が変わる厄介な一面がある。面倒くさがらずに、プロシージャの頭や変数宣言部では `DCL 変数名 属性 AUTOMATIC;` あるいは `STATIC;` を明示的にコーディングする習慣をつけろ。コードレビューで後輩のソースを見たとき、これが無かったら容赦なく差し戻しだ。
2. ストレージ・ダンプ(CEEUMP等)の読み方:
バッチが `S0C4`(ストレージ保護例外)などで落ちたとき、DumpmasterやCEEDUMPを覗き込むことになる。その際、スタック上に展開されている `AUTOMATIC` 変数のアドレス範囲と、ロードモジュール内の `STATIC` 領域のアドレス範囲の区別がつかないようでは、アーキテクトとしては三流だ。変数がスタックのどこに積まれていたかを追えるようになれば、ポインタ迷子や領域破壊のバグも一発で特定できるようになる。
メモリのライフサイクルを制する者は、PL/Iを制し、ひいてはメインフレームの基幹システムを制す。
今日のこの解説を胸に刻み、明日からの保守・改修作業に活かしてほしい。頼むぞ。
