【テクニカル・上級編】DIMビルトイン関数による配列要素数の算出 – PL/Iの基本構文とデータ制御実践ガイド

境界値の深淵を覗く:PL/I `DIM`関数とアスタリスク引数の動的真実

メインフレームの現場で長年生きていると、ふとした瞬間に「なぜこのコードは30年間一度も落ちずに動いているのか」と感嘆することがある。その心臓部で密かに、しかし確実に機能しているのが `DIM` ビルトイン関数だ。

今回は、現代のJavaやC#のエンジニアが移行時に最も頭を抱える「アスタリスク()指定された配列引数」の動的評価と、その背後でコンパイラが何を行っているのかを、アーキテクトの視点から紐解いていく。

1. 配列の「境界」を問う:DIM関数の本質

PL/Iにおいて `DIM(X, 1)` は単なる要素数のカウントではない。これは、コンパイラが生成する「記述子(Descriptor)」を参照し、メモリ上のどこからどこまでが有効な領域かを動的に計算する手続きだ。

特に、サブルーチン間で配列を渡す際、引数定義に `() ` を使うケースは多い。ここで `DIM` を呼び出したとき、コンパイラは呼び出し元が実際にどのようなメモリ配置で配列を渡してきたかを、記述子経由で逐一計算する。

/i
/ 配列の次元を動的に扱うプロシージャ例 /
PROCESS_ARRAY: PROCEDURE(INPUT_ARR);

/ 呼び出し元からアスタリスク付きで受ける /
DCL INPUT_ARR() FIXED BIN(31);
DCL I FIXED BIN(31);

/ DIM(INPUT_ARR, 1) は、呼び出し元の実引数の記述子を評価する /
DO I = 1 TO DIM(INPUT_ARR, 1);
/ ここでパックデシマル(DECIMAL FIXED)の演算を行う場合は注意が必要 /
/ 内部符号(X’0C’, X’0D’)の反転バグを避けるため、明示的な変換を推奨 /
INPUT_ARR(I) = INPUT_ARR(I) 2;
END;

END PROCESS_ARRAY;

なぜこれが「アベンド」の温床になるのか

もし、このプロシージャに記述子の情報を伴わない、あるいは意図しないメモリレイアウト(例えば、不適切なポインタ経由のポインタ渡しなど)で配列を渡すとどうなるか。`DIM` 関数は、不正なメモリ位置を「境界」と誤認し、ループの範囲を暴走させる。結果として、S0C4(アクセス例外)S0C7(データ例外) を引き起こす。ダンプ解析を行う際、この `DIM` が返した値が「本来あるべきはずの数」と一致しているかをレジスタから追いかけるのが、トラブルシューティングの定石だ。

2. マイグレーションにおける罠:ポインタと記述子

Javaへの移行を設計する際、最も危険なのは「PL/Iの配列は、単なるメモリの連続体ではない」という事実を軽視することだ。

`BASED` 変数とポインタを使い、`ADDR(ARRAY_VAL)` で特定のメモリ番地を指し示す際、PL/Iコンパイラは「記述子」というメタデータをどこに配置しているか意識する必要がある。

  • 記述子の有無: `DCL A() FIXED BIN` と `DCL A(10) FIXED BIN` では、コンパイラが生成するコードが全く異なる。前者は記述子を介した動的解決を行い、後者はスタック上の固定オフセットで計算する。
  • Java/C#への変換: これらの言語には「記述子」という概念は存在しない。そのため、移行ツールが自動生成するコードは、配列の長さ(length)を別途引数として渡す設計に強制変換される。このとき、元のPL/Iコードが「記述子から動的に境界を得ていた」ロジックの複雑さを過小評価すると、CICSのオンライン処理などで致命的なバッファオーバーフローを引き起こすことになる。

3. 実務で役立つ「鉄則」

現場のテックリードとして、以下の3点は心に刻んでおいてほしい。

1. パックデシマルの符号反転: `DIM` で計算したループ内で、DB2から取得したパックデシマルを計算する際、計算結果が意図せず負数(符号が反転)になることがある。これは `COMP-3` の内部表現を直接操作している場合に起きやすい。必ず計算前に `FIXED BIN` へのキャスト、あるいは `DECIMAL` 型の宣言の整合性を確認すること。
2. CICS環境での配列境界: CICS上で動くPL/Iプログラムにおいて、`DIM` 関数で取得した上限値をそのまま `EXEC CICS WRITE` の長さ指定に使う場合、必ず `IF` 文による妥当性チェックを挟め。通信エリア(COMMAREA)の定義長を超えたメモリ領域を `DIM` が指し示している場合、即座にトランザクションが異常終了(ABEND)する。
3. 最適化オプションの落とし穴: `OPTIMIZE(3)` を指定すると、ループ内での `DIM` 関数の評価が「不変」とみなされ、レジスタにキャッシュされることがある。メモリレイアウトをポインタで動的に書き換えるような「お行儀の悪い」コードを書いている場合、この最適化がバグを隠蔽、あるいは誘発する。

結びに代えて

PL/Iのコードを現代の言語へ移行するということは、単なる言語の変換ではない。それは、半世紀にわたってメインフレームのハードウェアと対話してきた「メモリ管理の哲学」を、新しい抽象化の世界へ翻訳する作業だ。

`DIM` 関数一つとっても、それが何を指し示し、コンパイラがどんな記述子を生成しているのかを理解していれば、どんな複雑なバッチ改修も恐れることはない。技術の深淵に触れる勇気こそが、大規模マイグレーションを成功させる唯一の鍵である。

次回は、`OFFSET` 型変数を用いたポインタ演算の危うさと、CICSにおけるストレージ・チェーンの破壊パターンについて深く掘り下げる予定だ。現場のエンジニア諸君、今日も堅牢なコードを書いてくれ。

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