【実務・中級編】POINTER属性とADDRビルトイン関数 – PL/Iの基本構文とデータ制御実践ガイド

お疲れ様です。今日も今日とて、山のようなスプール出力と格闘していることでしょう。

メインフレームの現場において、COBOLからPL/Iへ移行したプロジェクトや、あるいは古くからPL/Iで組み上げられた巨大な勘定系システムの保守を任されたとき、避けて通れないのが「ポインタ(POINTER)」と「メモリの直接参照」の世界です。

COBOLには本来(厳密な意味での)ポインタ操作やメモリのアドレスを直接いじる概念はありませんが、PL/IにはC言語並み、いやそれ以上に危険で強力なポインタ制御機構が標準備装備されています。特に31ビットアドレッシングからAMODE 64(64ビット)への移行期にある現代のメインフレーム実務において、この仕組みを誤解していると、原因究明に丸一日を費やすような難解なS0C4(プロテクション例外)や、データ破壊の沼にハマることになります。

今日は、ベテランの私から、PL/Iにおける `POINTER` 属性と `ADDR` ビルトイン関数の正しい作法、そして実務で絶対にやってはいけない禁忌について、現場の空気感を交えながら伝授しましょう。

—

1. PL/Iポインタの基本と「予約語を持たない」という思想

まず、PL/Iという言語のユニークな特徴について触れておきます。C言語などでは `int` や `if` といったキーワードが「予約語」としてガチガチに固められており、変数名として使うことはできません。しかし、PL/Iには厳密な意味での予約語が存在しません。

コンテキスト(文脈)によって識別子がキーワードか変数名かを判断するため、極端な話、`IF` という名前の変数すら定義できてしまいます(絶対にやらないでくださいが)。この「何でもアリ」な自由度の高さが、PL/Iのコードを時に難解にし、ポインタ操作をよりスリリングなものにしています。

その中で、メモリアドレスを保持する変数を宣言するのが `POINTER` 属性です。

1
DCL P_REC_PTR POINTER; / レコードを指し示すポインタ変数 /

このポインタが指す先にある実体を覗き見る(あるいはアドレスを取得する)ために使うのが、最強にして最凶のビルトイン関数 `ADDR` です。

—

2. ADDRビルトイン関数とベース変数の関係

`ADDR` 関数は、指定した変数のストレージ上の先頭アドレスを返します。例えば、VSAMの不安全なレコード領域や、ストレージ管理サブプールから取得した動的領域を指すために、ポインタと組み合わせて使います。

ここで実務のコード例を見てみましょう。VSAM(KSDS)のレコードを読み込み、その実体をベース変数(Based変数)を用いて効率的にマッピングする、よくあるバッチ処理のパターンです。

1
/ ========================================================== /
/ POINTER属性とBASED変数を用いたVSAMレコード効率処理サンプル /
/ ========================================================== /
TESTPROG: PROC OPTIONS(MAIN);

/ 1. レコードレイアウトの構造体定義(Based変数) /
DCL 1 M_CUSTOMER_REC BASED(P_CUST),
5 CUST_ID CHAR(8),
5 CUST_NAME CHAR(30),
5 CUST_STATUS CHAR(1),
5 FILLER CHAR(31);

/ 2. 変数・ポインタの宣言 /
DCL VSAM_FILE FILE RECORD INPUT;
DCL P_CUST POINTER;
DCL W_WORK_AREA CHAR(70) BASED(P_WORK);
DCL P_WORK POINTER;
DCL IO_STATUS FIXED BIN(15);

/ 3. ファイルオープン /
OPEN FILE(VSAM_FILE);

/ 4. レコード読込ループとポインタによるメモリ参照 /
DO FOREVER;
READ FILE(VSAM_FILE) INTO(W_WORK_AREA);
IF / 終了条件 / THEN LEAVE;

/ 読み込んだワークエリアのアドレスをベース変数に結びつける /
P_CUST = ADDR(W_WORK_AREA);

/ ADDRで取得したアドレスを通じてフィールドにアクセス /
IF CUST_STATUS = ‘A’ THEN DO;
PUT SKIP EDIT (‘ACTIVE CUST:’, CUST_ID, CUST_NAME)
(A, X(1), A, X(1), A);
END;
END;

CLOSE FILE(VSAM_FILE);
RETURN;

END TESTPROG;

このコードでは、`W_WORK_AREA` という単なる70バイトの文字変数に対し、`P_WORK` ポインタと `ADDR` 関数を経由して、構造体 `M_CUSTOMER_REC` のレイアウトをガバッと重ね合わせて(Based定義して)処理しています。これがPL/Iの真骨頂であり、C言語のキャスト(Type Casting)に近い強力なメモリ操作です。

—

3. 31ビットから64ビットアドレッシングへの移行とポインタの罠

さて、ここからが本題であり、現代のメインフレームエンジニアが最も注意しなければならないポイントです。

IBM z/OSの進化に伴い、アプリケーションは従来のAMODE 31(31ビットアドレッシング)から、巨大なメモリ空間を扱えるAMODE 64(64ビットアドレッシング)への移行が進んでいます。

ここで何が起きるか?

1. ポインタのサイズが変わる

  • 31ビットモードでは、ポインタは 4バイト(32ビット、上位1ビットはフラグ用等) でした。
  • 64ビットモードでは、ポインタは 8バイト(64ビット) になります。

2. ハードコードされたアドレスの崩壊

  • 昔の古いソースコードや、力技のデバッグロジックで、ポインタやアドレスを `FIXED BIN(31)`(4バイトの数値)として保持し、それに直接数値を足し引きしてオフセット計算している悪質なコードが時々見つかります。
  • これを64ビット環境(AMODE 64)で動かすと、`FIXED BIN(31)` に格納しきれずにアドレスの上位ビットが切り捨てられ、瞬時に S0C4(ABEND S0C4 – 記憶域保護例外) が発生します。

危険なコードのアンチパターン

以下のような、ポインタを数値にキャストして無理やり演算するようなコードは、レガシー移行の現場において「踏み絵」になります。

1
/ 【警告】絶対に真似してはいけない危険なコード /
Dcl P_DATA Pointer;
Dcl L_ADDR Fixed Bin(31); / 64ビット環境では破綻する! /

L_ADDR = Addr(W_WORK_AREA);
L_ADDR = L_ADDR + 100; / 100バイト先を無理やり指す /
P_DATA = Ptr(L_ADDR); / (※実際にはPL/Iの組み込み関数等で変換) /

現代のPL/Iでは、ポインタの演算は生のアドレス数値をいじるのではなく、Based変数の構造体定義や、ビルトイン関数による適切なポインタ演算(OFFSET属性やPTRADDなど)を使用するのが鉄則です。

—

4. ONユニットとポインタ異常時の防御的プログラミング

万が一、不正なメモリアドレスを参照してしまい、S0C4やS0C1といったシステム異常終了(ABEND)を引き起こしそうな場合、バッチ基盤としては夜間バッチ全体を止めるわけにいかないケースもあります。

ここで、PL/Iの誇る強力な例外処理機構であるONユニットの出番です。ポインタ起因のストレージ違反を捕捉し、ジョブを安全に異常終了させる、あるいはログを出してバイパスするための設計を組むことが可能です。

1
/ ストレージ保護例外(S0C4等に相当)の捕捉例 /
ON STORAGE / または ERROR / CONDITION /
BEGIN;
PUT SKIP EDIT (‘ 致命的なメモリ参照エラーを検知しました ‘) (A);
/ 異常終了コードを設定して安全にロールバック・終了する処理 /
CALL PLIRETC(16);
CLOSE FILE(VSAM_FILE);
FINISH;
END;

もっとも、ONユニットでエラーを無理やり握りつぶすのは悪手です。ポインタと `ADDR` 関数を扱うときは、そもそも「不正なアドレスを指さない」ような厳密な境界チェックを行う防御的コーディングが大前提となります。

—

5. ベテランからのアドバイス:保守現場での鉄則

大規模改修やバージョンアップの調査でPL/Iのソースコードを読む際、`POINTER` と `ADDR` が出てきたら、以下のチェックリストを必ず頭に思い浮かべてください。

1. そのポインタは本当に必要か?

  • ただのレコード構造の再解釈であれば、通常のストラクチャや `OVERLAY` 属性、あるいは単なる文字の切り出し(SUBSTR)で安全に書けないか検討する。ポインタは最後の手段です。

2. AMODE 64対応の妨げになっていないか?

  • ポインタやアドレスを `FIXED BIN(31)` などの整数型に無理やり代立させていないか、コンパイラオプションやデータ定義を徹底的に洗う。

3. ストレージのライフサイクルは正しいか?

  • `ALLOCATE` で取得したメモリが `FREE` されずにメモリリークを起こしていないか、あるいは既に `FREE` された領域をポインタが指し続けていないか(ダングリング・ポインタ)。

PL/Iのポインタと `ADDR` 関数は、マシンの性能を極限まで引き出すための諸刃の剣です。言語仕様の裏側にあるメモリ管理のメカニズムを正しく理解し、後輩たちに胸を張って引き継げる、美しく堅牢なコードベースを維持していきましょう。

それでは、次のスプールチェックに戻ります。健闘を祈る!

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