皆さん、こんにちは。長年、この巨大なメインフレームの腹の中でうごめくPL/Iバッチ群と格闘してきたシニア・アーキテクトの私だ。
近年のマイグレーション案件やレガシーシステムのブラックボックス化解消において、若手エンジニアから最も悲鳴に近い質問を受けるのが、実は「ポインタとオフセットの絡むポインタ演算、そしてArea(領域)管理」の領域だ。C言語の感覚で `ptr + offset` なんて適当に書いていると、深夜のバッチ窓口で盛大に `S0C4`(ストレージ保護例外)を叩き出し、運用オペレータから冷たい視線を浴びることになる。
今回は、PL/Iの最大の特徴の一つであり、かつ最も誤解されやすい「OFFSET関数とPOINTER関数の相互変換」について、Area管理との深遠な依存関係を交えながら、現場のノウハウを総動員して徹底的に解説しよう。
—
1. なぜPL/IにはOFFSETとPOINTERの2種類があるのか?
現代のオープン系言語(JavaやC#など)しか知らない世代からすると、「メモリアドレスを指すなら、ポインタだけで十分じゃないか」と思うかもしれない。しかし、31ビット(あるいは64ビット)アドレッシングの世界で生きる我々のメインフレームシステム、特にCICSや大規模バッチにおけるストレージ管理では話が違う。
絶対アドレス(POINTER)の呪縛
`POINTER`型変数が保持するのは、仮想記憶上の絶対アドレスだ。
しかし、この絶対アドレスをそのままDASD(外部記憶装置)のデータセットに書き出したり、異なるアドレス空間を持つタスク間でそのまま渡したりすることはできない。なぜなら、次にそのプログラムがロードされたとき、あるいは別のアドレス空間で展開されたとき、その絶対アドレスは完全に無効(あるいは別人のメモリ領域)になっているからだ。
相対位置(OFFSET)という救世主
そこで登場するのが `OFFSET` 型だ。
`OFFSET` 型変数が保持するのは、絶対アドレスではない。あるArea(エリア)の先頭からの相対バイト数(オフセット値)である。
つまり、AreaごとVSAMファイルに吐き出そうが、ストレージプール内でゴソッと移動させようが、Areaの先頭からの相対位置さえ変わっていなければ、ファイルから再読み込みした瞬間に再び正しくリンクを辿ることができるのだ。ここが、レコード入出力やVSAMアクセス、さらには複雑なリスト構造体を扱う際の肝となる。
—
2. Area管理の基本とOFFSET/POINTERの相互変換
PL/Iの `AREA` は、特定のストレージプールを自己完結型で管理するための強力な仕組みだ。このArea内でのみ有効なのが `OFFSET` であり、Areaの外やシステム全体を指すのが `POINTER` である。
この2つを相互に変換するために、PL/Iには組み込み関数(BUILTIN)として `POINTER` 関数と `OFFSET` 関数が用意されている。
- `POINTER (offset_var, area_var)`
- Area内の相対位置(OFFSET)から、現在の実行時における絶対アドレス(POINTER)を計算して返す。
- `OFFSET (pointer_var, area_var)`
- 絶対アドレス(POINTER)から、指定されたAreaの先頭を基準とした相対位置(OFFSET)を計算して返す。
これらは単なる型キャストではなく、「指定されたAreaの先頭アドレスを基準にした算術演算」をコンパイル時コード(またはランタイム処理)として埋め込むものだ。したがって、もし変換時に指定したAreaの範囲外を指していれば、容赦なく異常終了を引き起こす。
—
3. 実践コード:VSAM/レコード入出力を見据えたリスト構造の制御
百聞は一見に如かず。実際のバッチプログラムでよく見られる、Area内でのリスト構造構築、そしてOFFSETとPOINTERの相互変換を行うサンプルコードを見てほしい。大文字ベース、適切なインデント、そして現場で泣かないためのコメントを添えている。
1
—————————————————————-;
- 領域(AREA)内でのオフセット管理とポインタ変換を行うサンプル ;
—————————————————————-;
MAPPED_AREA: PROC OPTIONS(MAIN);
DCL 1 NODE_TYPE BASED,
5 NEXT_OFF OFFSET(MY_AREA), / 次ノードへのオフセット /
5 DATA_VAL FIXED BIN(31); / 有効データ /
DCL MY_AREA AREA(4096) BASED(AREAPTR); / 4KBの管理領域 /
DCL AREAPTR POINTER; / 領域自体の絶対基底 /
DCL P_CUR POINTER; / 作業用絶対ポインタ /
DCL P_NEXT POINTER; / 次用絶対ポインタ /
DCL OFF_CUR OFFSET(MY_AREA); / 作業用オフセット /
DCL I FIXED BIN(31);
/ 1. ワーキングストレージ上にAREA用のメモリを獲得 /
ALLOCATE MY_AREA;
/ AREAの先頭を指すポインタを初期化 /
AREAPTR = ADDR(MY_AREA);
/ 2. AREA内に最初のノード(ルート)を割り当て /
ALLOCATE NODE_TYPE IN(MY_AREA);
/ 最初のノードの絶対アドレスを取得し、データ設定 /
P_CUR = ADDR(NODE_TYPE);
P_CUR->DATA_VAL = 100;
P_CUR->NEXT_OFF = NEXT_OF_NONE; / 終端マーク(便宜上) /
/ 3. ループでノードを追加していく(実務でのトランザクション処理等) /
DO I = 200 TO 400 BY 100;
/ 新規ノードをAREA内に確保 /
ALLOCATE NODE_TYPE IN(MY_AREA);
P_NEXT = ADDR(NODE_TYPE);
P_NEXT->DATA_VAL = I;
P_NEXT->NEXT_OFF = OFFSET(NULL(), MY_AREA); / 旦NULLクリア /
/ 現在のノードのNEXT_OFFに、新ノードのOFFSETを設定する /
/ ここが重要:絶対アドレス(P_NEXT)をAREA基準のOFFSETに変換 /
P_CUR->NEXT_OFF = OFFSET(P_NEXT, MY_AREA);
/ カレントを次に進める /
P_CUR = P_NEXT;
END;
/ 4. 保存・出力時を想定したトラバーサル(走査) /
PUT SKIP EDIT (‘— AREA内リストの走査開始 —‘) (A);
/ 先頭ノードの絶対アドレスを復元するには? /
/ 実際にはAREA先頭からのポインタを計算する /
/ ここでは簡易的に最初のALLOCATEの位置を知っている前提でポインタを辿る /
————————————————————;
- 注意: 実際のマイグレーションやVSAM出力では、このMY_AREA自体
- をまるごとレコードとしてWRITE/PUTするため、内部の
- ポインタはすべてOFFSETで保持されている必要がある。
————————————————————;
/ 5. 終了処理 /
FREE MY_AREA;
RETURN;
END MAPPED_AREA;
—
レイアウトやポインタの絡むコーディングで、現場のプログラマーがよくハマる罠についても触れておこう。
デバッグのコツとONユニットによる異常系制御
もしAreaの容量(今回の例では `AREA(4096)`)を超えて `ALLOCATE NODE_TYPE IN(MY_AREA)` を実行した場合、PL/Iランタイムは `AREA` 条件(Condition)を発生させる。
これを捕捉せずに放置するとバッチは異常終了(U4038など)するが、保守現場の堅牢なバッチでは以下のように `ON AREA` ユニットを仕込んで、ファイルクローズや代替処理へ安全に誘導するのがプロの技だ。
1
ON AREA(MY_AREA) BEGIN;
PUT SKIP EDIT (‘ERROR: ストレージエリアが枯渇しました。’) (A);
/ 必要に応じたダンプ採取やエラーハンドリング /
GOTO AREA_OVERFLOW_ROUTINE;
END;
また、 `POINTER(offset_var, area_var)` を使う際、もし `offset_var` がヌルオフセット(PL/Iでは `NULL()` やゼロ相当)である場合、正しく処理されないか、あるいはコンパイラオプションや処理系によっては無効アドレスを参照してしまうことがある。変換前には必ず `NULL()` 比較を入れるのが鉄則だ。
1
IF P_CUR->NEXT_OFF = OFFSET(NULL(), MY_AREA) THEN …
※厳密には `OFFSET` 型同士の比較、あるいは `NULL()` との比較は言語仕様上サポートされているが、処理系のバージョンによって挙動の微差があるため、実務では `NULL()` 判定専用のマクロや条件分岐を挟むのが安全である。
—
4. シニアアーキテクトからのメッセージ
メインフレームのPL/Iプログラムは、何十年も前に先輩たちが血ードを絞って作り上げた資産の塊だ。構造体をそのままVSAMやBDAMに落とし込むために `OFFSET` と `AREA` を組み合わせた先人の知恵は、現代のメモリ管理の視点から見ても非常に洗練されている。
「なぜここは `POINTER` ではなく `OFFSET` なのか?」
その疑問を持った瞬間から、君は単なる「コードのコピペ屋」から「真のメインフレーム・アーキテクト」への階段を登り始めている。
改修作業や調査でこの記述に出会ったら、焦らずに「このAreaはどこからどこまでをスコープとしているのか」「外部ファイルに書き出される前提の構造体ではないか」という文脈を読み解いてほしい。地道な読み解きこそが、基幹システムを支えるエンジニアの最大の武器なのだから。
