【実務・中級編】ADDR関数とPOINTER関数のアドレス解決 – PL/Iの基本構文とデータ制御実践ガイド

予約語を持たないPL/Iの懐の深さ:その代償とアドレス解決の真実

おい、最近入った若手が「先輩、PL/Iって `IF` とか `READ` を変数名に使ってもエラーにならないんですけど、これってバグですか?」って聞いてきやがった。

思わず苦笑いしたね。COBOLやC言語しか知らない世代からすると、言語のキーワード(予約語)を変数名に使えないのが常識だから、PL/Iのこの仕様は奇妙に映るらしい。
だが、これがPL/Iの歴史であり、同時に厄介な特徴だ。PL/Iには「文脈依存の予約語」しか存在しない。つまり、コンパイラは前後の文脈を見て、「あ、ここでは `IF` は変数名じゃなくて制御文だな」と判断している。

しかし、この懐の深さは、時として我々プログラマに牙を向く。特にポインタを扱い出し、ストレージの生のアドレスを直接いじり始めると、コンパイラがどうやってメモリー上の変数を探し当てているのかという「裏側の仕組み」を理解していないと、突如として発生する S0C4(ソックスヨン)異常終了 の闇に迷い込むことになる。

今回は、PL/Iにおける `ADDR` 関数と `POINTER` 関数を使ったアドレス解決のメカニズム、そしてハードウェア(S/370アーキテクチャ以降のベース・ディスプレイスメント方式)が裏で何をやっているのかを、実務の現場目線で徹底的に叩き込んでやろう。

—

1. ハードウェアの現実:ベース・ディスプレイスメントとPL/Iポインタ

メインフレーム(IBM Z)の機械語命令は、メモリーを参照するときに必ず 「ベースレジスタ(Base Register:16bitのうちの基準アドレス)」+「変位(Displacement:12bitのオフセット値、最大4095バイト)」 という方式を取る。

C言語やJavaに慣れた連中は「ポインタ=ただの32ビット(あるいは64ビット)の数字」と思いがちだが、PL/Iの `POINTER` 型(あるいは `OFFSET` 型)が保持しているのも、本質的にはこのメモリー上の絶対アドレスだ。

しかし、PL/Iのコードで以下のように変数をポインタ経由で操作するとき、コンパイラとランタイムは裏でどのような計算を行っているのだろうか?

DCL P PTR;
DCL 1 CUST_REC BASED(P),
2 CUST_ID CHAR(8),
2 CUST_NAME CHAR(30),
2 CUST_BAL DEC FIXED(9,2);

`CUST_REC` のようなBASED変数(基底付き変数)をアクセスする際、PL/Iコンパイラはポインタ変数 `P` の値をベースレジスタ(あるいはそれに相当するレジスタ)にロードし、各フィールド(`CUST_ID`, `CUST_NAME`, `CUST_BAL`)までのオフセット(変位)を自動的に計算して機械語命令(MVCやLなど)を組み立てている。

この「オフセットの計算ミス」や「ポインタ自体の未初期化(ロカケ)」が、夜間バッチを盛大にクラッシュさせる原因の第1位なのだ。

—

2. 実践:VSAMレコードの動的割り当てとADDR/POINTERの活用

百聞は一見にしかずだ。実際の基幹系バッチでよくある、VSAM(KSDS)ファイルを読み込み、レコード内の特定フィールドのアドレスを `ADDR` 関数で取得し、別のポインタ変数経由で細工するサンプルコードを見せておこう。

このコードは、大文字記述、適切なインデント、そして `BUILTIN` 属性の宣言という、我々が守るべきコーディング標準に則っている。

/================================================================/
/ プログラムID : MVSAM01P /
/ 概要 : VSAM(KSDS)読込とADDR/POINTER関数によるアドレス解決 /
/================================================================/
MVSAM01P: PROC OPTIONS(MAIN);

/ ビルトイン関数の明示的宣言(保守時の可読性と誤認防止のため必須) /
DCL ADDR BUILTIN;
DCL POINTER BUILTIN;
DCL LENGTH BUILTIN;

/ 変数およびファイルの定義 /
DCL V_FILE FILE RECORD INPUT;
DCL EOF_FLG CHAR(1) INIT(‘0’);
DCL RET_CD FIXED BIN(31) INIT(0);

/ VSAMレコードに対応するBASED変数の定義 /
DCL P_REC PTR;
DCL 1 MASTER_REC BASED(P_REC),
3 REC_KEY CHAR(6), / 顧客コード /
3 REC_DATA CHAR(74); / 実データ領域 /

/ ワーク用ポインタとストラクチャ /
DCL P_WORK PTR;
DCL 3 WORK_AREA BASED(P_WORK),
5 W_PREFIX CHAR(2), / ワーク接頭辞 /
5 W_BODY CHAR(72); / ワーク本体 /

/ オンユニットによるファイル終了(ENDFILE)の制御 /
ON ENDFILE(V_FILE) EOF_FLG = ‘1’;

/ VSAMファイルのオープン /
OPEN FILE(V_FILE);

DISPLAY(‘ MVSAM01P 処理開始 ‘);

/ 初回レコード読み込み /
READ FILE(V_FILE) SET(P_REC);

DO WHILE (EOF_FLG = ‘0’);

/————————————————————/
/ ADDR関数の実践利用: /
/ MASTER_RECのREC_DATA部分のメモリー上の正確なアドレスを取得 /
/————————————————————/
P_WORK = ADDR(REC_DATA);

/ 取得したアドレス(P_WORK)から逆算してデータを加工 /
W_PREFIX = ’99’; / 接頭辞を強制設定 /

/ POINTER関数の実践利用: /
/ 別のアドレスベースに対して強制的にポインタを再構築する例 /
/ (※実務ではストレージレイアウトの整合性に十分注意すること) /
/ P_WORKが指すアドレスから特定のオフセットを計算して検証 /
IF W_PREFIX = ’99’ THEN DO;
/ ここで何らかのビジネスロジックを実行 /
END;

/ 次のレコード読み込み(SETオプションによりポインタが自動更新) /
READ FILE(V_FILE) SET(P_REC);

END;

/ ファイルのクローズ /
CLOSE FILE(V_FILE);

DISPLAY(‘ MVSAM01P 正常終了 ‘);
RETURN;

END MVSAM01P;

—

3. ここが現場の勘所:ADDRとPOINTERを扱う際の注意点

上記のコードを見て、「おっ、`ADDR(REC_DATA)` でフィールドの先端アドレスが簡単に取れるなら楽勝だな」と思ったそこのお前。甘い。実務では以下の罠に嵌って徹夜する羽目になる。

① `ADDR` 関数は「ポインタ」を返すのではなく「ポインタ型(PTR)」の値を返す

`ADDR(x変数の名前)` は、その変数が現在割り当てられているストレージ上のアドレスを `PTR` 型として返す。
ここで注意すべきは、`BASED` 変数に対して `ADDR` を使う場合だ。ベースとなるポインタ(上のコードなら `P_REC`)が `NULL()` を指している状態で `ADDR` を呼び出すと、コンパイルは通っても実行時に S0C4異常終了(保護例外) が発生する。なぜなら、ベースアドレスが存在しないのに、その中のフィールドの相対位置(オフセット)を計算しようがないからだ。

② `POINTER` 関数の本質とアライメント(境界調整)

PL/Iの `POINTER(x, y)` ビルトイン関数(※コンパイラやバージョンにより仕様が異なるが、一般にポインタの計算やストレージ間のオフセット操作に使われる)を扱う際、ハードウェアの境界制約(半ワード境界、フルワード境界、ダブルワード境界)を無視したアドレス操作を行うと、S0C4やS0C6(仕様例外)の餌食になる。
特に `FIXED BIN(31)` やフロート型を奇数アドレスや半端なオフセットにバインドしようものなら、System z の CPU は容赦なくタスクをアボートさせる。メインフレームはC言語のように「勝手にバイトアライメントを調整してよしなにやってくれる」ほどお人好しではないのだ。

③ ONユニットとの組み合わせにおけるアドレスの寿命

ファイル入出力や動的記憶域の解放(`FREE` 組み込み関数)を行う際、古いポインタ値を保持し続けたままアクセスすると、すでに解放された(あるいは再利用された)別のプログラムの領域を書き換えるという、最も厄介で原因特定が困難な「メモリ破壊バグ」を引き起こす。
`ON ERROR` や `ON STORAGE` などのONユニットを組むときは、ポインタの有効範囲(スコープ)とライフサイクルを常に意識しろ。

—

先輩からのアドバイス:デバッグの極意

もし君が担当しているバッチプログラムが `ABEND S0C4` で落ちて、シスログ(Syslog)やDUMPリストに「Program Check Interruption」と吐き出されていたら、まず疑うべきは以下の3点だ。

1. ポインタ変数が `NULL()` のまま `BASED` 変数を参照していないか?
2. `READ … SET(P)` でファイルの終端(ENDFILE)を抜けた後に、そのポインタをデリファレンスしていないか?
3. `ADDR` で取得したアドレスに対して、想定外のオフセット加算(ポインタ演算のミス)を行っていないか?

PL/Iのポインタとアドレス解決のメカニズムは、ハードウェアの構造と直結しているからこそ、一度マスターすればC言語の低水準プログラミングにも通じる揺るぎない技術的基盤になる。
「予約語がない」という自由度の高さに溺れず、コンパイラが裏で何を考え、IBM Zのハードウェアがどう動いているのかを頭に思い浮かべながらコードを書くことだ。

さて、理論はここまでだ。さっそくテストJCLを組んで、動かして確かめてみろ。エラーが出たら、また俺のところに聞きに来い。

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