PL/Iの「スコープ」を制する者は、メインフレームの深淵を制す ― BEGINとPROCEDUREの境界線
メインフレームの現場で何十年と生き抜いてきた諸君なら、一度は目にしたことがあるだろう。「なぜ、この変数の値が予期せず書き換わっているのか?」という悪夢のような調査依頼を。
PL/Iという言語は、その自由度の高さゆえに、現代の厳格なJavaやC#の設計思想から見れば「野放図」に見えるかもしれない。しかし、この言語のスコープ設計――特に`PROCEDURE`と`BEGIN`ブロックの挙動――を正しく理解することは、基幹システムの保守・移行において避けては通れない「急所」だ。
今日は、単なる文法解説ではない。この言語の裏側にある「メモリの血脈」について語ろう。
—
1. PROCEDUREとBEGIN:見えない境界線の正体
まず、基本を整理しよう。`PROCEDURE`は独立したセグメントであり、呼び出しのたびに新しいアクティベーション・レコード(スタックフレーム)が構築される。対して`BEGIN`ブロックは、現在の`PROCEDURE`のコンテキストを共有しつつ、一時的なスコープを作成する。
ここで最も勘違いされやすいのが、自動変数(Automatic Variables)の生存期間だ。
/i
/ BEGINブロックのスコープ特性を活かした一時的なバッファ制御 /
MAIN_PROC: PROCEDURE OPTIONS(MAIN);
DCL MSG CHAR(20) INIT(‘GLOBAL’);
BEGIN;
/ ここでMSGを再定義すると、このブロック内では別個の変数として扱われる /
DCL MSG CHAR(20) INIT(‘LOCAL_BLOCK’);
PUT SKIP LIST(MSG); / 出力: LOCAL_BLOCK /
END;
PUT SKIP LIST(MSG); / 出力: GLOBAL /
END MAIN_PROC;
一見、何でもないコードだが、マイグレーション時にこの「動的な変数再定義」を自動変換ツールに任せると、思わぬバグが混入する。特に`CICS`や`IMS`のようなオンライン環境で、`BEGIN`ブロック内に大量のスタック変数を配置すると、スタックオーバーフロー(S0C4やS0C3の温床)を引き起こすリスクがあることを忘れてはならない。
—
2. ONユニットと動的スコープ:罠に落ちる瞬間
PL/Iの真骨頂は、`ON`ユニットによる動的なエラーハンドリングにある。ここで重要なのは、`ON`ユニットが「どの時点のスコープを参照しているか」だ。
`BEGIN`ブロック内で定義された`ON`ユニットは、そのブロックを抜けると無効化される。しかし、`PROCEDURE`内で定義されたものは、スタックを遡及して有効であり続ける。
/i
/ ゼロ除算を検知するONユニットの動的スコープ /
ON ZERODIVIDE BEGIN;
/ ダンプ出力やログ送出の共通処理 /
PUT SKIP LIST(‘ゼロ除算発生: ダンプ解析を開始’);
SIGNAL FINISH; / 強制終了 /
END;
/ このブロック内で発生した例外は、上記のONユニットに補足される /
CALL CALC_LOGIC();
アーキテクトの視点:
この動的スコープの挙動を過信すると、異常終了時のスタックトレースが複雑怪奇になる。特に`DB2`や`SQL`の埋め込み処理で`WHENEVER SQLERROR`の代わりに`ON`ユニットを使う際、ブロックの階層が深いと「どの例外を誰が拾っているのか」が追えなくなる。バッチの改修時には、必ず`ON`ユニットの定義範囲をコメントで明記する習慣を強く推奨する。
—
3. ポインタ、基底変数、そして「見えない符号反転」
マイグレーション案件で最も頭を抱えるのが、`BASED`変数とポインタを使った領域操作だ。レガシーコードでは、共有メモリ領域をポインタでなぞり、`FIXED BINARY`や`PACKED DECIMAL`を直接操作する手法が多用される。
ここで最も恐ろしいのが、パックデシマル(COMP-3)の符号反転バグだ。
- 現象: `REDEFINE`で別の型として参照した際、符号ビット(C/D/F)が意図せず書き換わる。
- 対策: `UNSPEC`関数や`ADDR`関数を使ってビットレベルで解析する際、必ず`COMPILER OPTION`の`LANGLVL(SAA2)`や`DBCS`設定が整合しているかを確認せよ。
現代のコンパイラは優秀だが、古いコードの「型を無視したメモリ読み込み」に対しては、最適化エンジンが予測不可能な挙動を示すことがある。`OPTIMIZE(2)`以上を適用する際は、`VOLATILE`属性の付与を検討すべきだ。
—
4. ABEND(アベンド)と対峙するために
最後に、アベンド時のダンプ解析について。
`S0C7`(データ例外)が出たとき、諸君はどこを見る? ほとんどのエンジニアは`SYSUDUMP`のレジスタ値だけを見て諦める。
だが、真のプロフェッショナルは、「スタック上のどの変数が、どの命令で破壊されたか」を追跡する。`PROCEDURE`の入口で`ENTRY`属性に`OPTIONS(ASSEMBLER)`を混ぜてアセンブラ言語とのリンクを見たり、`STORAGE`クラスを確認して、変数が`BEGIN`ブロックの境界を越えてメモリ破壊を起こしていないかを特定する。
結び:未来への架け橋として
PL/IからJava等への移行は、単なるコードの書き換えではない。それは、数十年前のエンジニアがメモリの1バイト、1ビットを削って捻り出した「工夫」を、現代の抽象化された世界へ翻訳する作業だ。
この言語の複雑なスコープ挙動を理解することは、レガシーを「古い遺物」として切り捨てることではなく、その設計思想の根幹にある「制御の厳密さ」を継承することに他ならない。
諸君、次のマイグレーションでは、ツールが吐き出したコードを盲信するな。コンパイラが何を考え、メモリがどう動いているのか。その「声」を聞け。それが、真のシステムアーキテクトへの道だ。
