PL/Iの「初期化の罠」— INITIAL属性の挙動と、現代的システム移行への警鐘
メインフレームのコードベースを紐解くと、時折、設計者の意図が見えなくなる箇所に遭遇する。PL/Iという言語は、その柔軟性と「予約語を持たない」という仕様ゆえに、記述者によって全く異なる表情を見せる。今回は、その中でもバグの温床となりやすく、かつマイグレーション時にJavaやC#のエンジニアを絶望させる「INITIAL属性」の深淵について語ろう。
1. INITIAL属性:宣言時と再呼び出し時の「非対称性」
まず大前提として、PL/Iの `INITIAL` 属性は、単なる「初期値の代入」ではない。これはコンパイラに対し、「この変数が有効範囲(Scope)に入るたびにこの値をロードせよ」という命令を送るものだ。
ここで問題になるのは、`PROCEDURE` 呼び出しごとの挙動だ。
1
/ 典型的なバグの温床:静的初期化の誤解 /
TEST_PROC: PROCEDURE;
DCL COUNT FIXED BIN(31) INITIAL(0);
COUNT = COUNT + 1;
PUT LIST(COUNT);
END TEST_PROC;
このコードは、`TEST_PROC` が呼び出されるたびに、`COUNT` は常に `1` となる。C言語などの `static` 変数のような挙動を期待して「累積計算」をさせているコードが、マイグレーション先で「なぜか値がリセットされる」と騒ぎになるのは、この特性を無視しているからだ。
もし、呼び出し間で値を保持させたいのであれば、`STATIC` 属性を明示的に付与しなければならない。
1
/ STATICを付与することで、セグメント内での永続性を担保する /
DCL COUNT FIXED BIN(31) STATIC INITIAL(0);
2. ポインタと動的メモリ操作が招く「初期化の不整合」
私がアーキテクトとして現場で最も恐れるのは、`BASED` 変数と `INITIAL` 属性の組み合わせだ。`ALLOCATE` 文でメモリを確保した際、`INITIAL` で指定した値は「その領域を確保した瞬間」に一度だけ書き込まれる。
しかし、`POINTER` を駆使して複雑な構造体を操作する際、メモリを `FREE` して再 `ALLOCATE` した場合、コンパイラや最適化オプション(`OPTIMIZE(3)`など)によっては、古いメモリ領域の残骸(ゴミ)が初期化を阻害するかのように見える挙動を示すことがある。
特に、DB2からの取得結果を `BASED` 変数に格納する際、初期化が不完全なままだと、後続のプログラムがその「残骸」を有効なデータと誤認し、オンライン処理のCICSで `ASRA` アベンドを叩き出す。この解析には、ダンプの16進ダンプと、コンパイラリストのオフセット情報を突き合わせるという、地道だが確実な作業が不可欠だ。
3. パックデシマルと「負のゼロ」という亡霊
初期化に関連して、`FIXED DEC`(パックデシマル)の扱いには神経を尖らせる必要がある。
1
DCL AMT FIXED DEC(9, 2) INITIAL(0);
この `0` の初期化は非常に安全に見えるが、外部システムから受け取ったデータが「パックデシマルの内部符号(最後のニブル)」において、本来あるべき `X’C’`(正)ではなく、意図せず `X’D’`(負)として初期化された状態で演算に回ると、比較命令で予期せぬ結果を生む。
特にレガシー移行でJavaへ変換する場合、`BigDecimal` クラスは「負のゼロ」を許容しないケースが多い。移行元で「符号フラグが立っているが値は0」というデータが混入している場合、Java側での厳密なチェックを実装しなければ、基幹業務の計算整合性が崩壊する。
4. 移行スペシャリストへの提言:設計の解像度を上げろ
モダンな言語へのマイグレーションを担当するなら、以下の3点を徹底していただきたい。
1. コンパイラ最適化の影響を疑え: `OPTIMIZE` オプションが有効な場合、ループ内での変数初期化がインライン展開され、デバッガの挙動がソースコードと一致しなくなることがある。
2. 埋め込みSQLのホスト変数: `EXEC SQL` で使用する構造体は、`INITIAL` に頼らず、必ず `EXEC SQL WHENEVER` や、個別の初期化処理を明示的に記述する癖をつけよ。暗黙の初期化は、将来のメンテナンス担当者への「技術的負債」という名の爆弾だ。
3. ダンプ解析は「変数の生存期間」から: アベンド発生時、変数が「どこで初期化され」「どこで汚染されたか」を追うためには、スタック上の変数の配置(`MAP` オプションで出力されるリスト)を読み解くスキルが不可欠だ。
PL/Iは古いが、その仕様の深さは現代のプログラミング言語でも到達しえない領域にある。コードの表面をなぞるのではなく、コンパイラが裏でどのような命令列を生成し、メインフレームのメモリ空間上で何が起きているのか。その「想像力」を持てない者に、レガシーシステムの移行は決して任せられない。
現場でアベンドと格闘している諸君、エラーログの向こう側にいるのは「神」ではなく「コンパイラ」だ。論理的に追えば、必ず答えは見つかる。健闘を祈る。
