メインフレームの深淵へ:PL/I `DEFINED` 属性とメモリ再定義の「危うい美学」
長年、勘定系や基幹システムのバッチ改修を渡り歩いてきた身からすると、PL/Iという言語は「古いが、極めて論理的で強力なツール」だ。COBOLの `REDEFINES` に相当する機能として、PL/Iには `DEFINED` 属性が存在する。
しかし、この `DEFINED`、単に「同じメモリを別の名前で呼ぶ」だけの代物ではない。使い方を誤れば、アライメント(位置合わせ)の罠に足を取られ、突如として不可解なデータ化けや、本番環境でのS0C4エラー(記憶保護例外)を招くことになる。
今日は、現場で後輩によく説く「メモリ再定義の正しい作法」について、実装視点で語っていこう。
—
1. なぜ今、DEFINEDが必要なのか
現代のシステム改修において、`DEFINED` を使う局面は主に「レコード入出力」だ。VSAMファイルから読み込んだ固定長レコードを、異なるレイアウトで解釈し直す際、あるいはデータ転送バッファを構造化して扱う際に、この属性は非常に有用だ。
COBOLの `REDEFINES` と決定的に違うのは、PL/Iの `DEFINED` は、基底変数に対して「どのような位置関係でオーバーレイさせるか」を柔軟に定義できる点にある。
2. 実践的な実装コード:メモリを操る作法
以下のコードは、VSAMから読み込んだ100バイトのワークエリアを、異なる視点(ヘッダー部と明細部)で再定義する例だ。
/i
/ —————————————————————– /
/ プログラム名: DEFINED属性のサンプル /
/ —————————————————————– /
TEST_PROG: PROCEDURE OPTIONS(MAIN);
/ 基底となる共通ワークエリア /
DCL WORK_AREA CHAR(100) INIT((100)’ ‘);
/ ヘッダー定義: WORK_AREAの先頭から定義 /
DCL 1 HEADER_REC DEFINED(WORK_AREA) POS(1),
3 RECTYPE CHAR(2),
3 SEQ_NUM FIXED BIN(15);
/ 明細定義: WORK_AREAの10バイト目から定義 /
DCL 1 DETAIL_REC DEFINED(WORK_AREA) POS(10),
3 ITEM_CD CHAR(8),
3 AMOUNT FIXED DEC(9, 2);
/ ————————————————————- /
/ 処理開始 /
/ ————————————————————- /
/ 構造体に値を代入し、メモリが共有されていることを確認する /
HEADER_REC.RECTYPE = ‘HD’;
HEADER_REC.SEQ_NUM = 1;
DETAIL_REC.ITEM_CD = ‘A1234567’;
/ BUILTIN関数によるデバッグログ /
PUT SKIP LIST(‘REC TYPE:’, HEADER_REC.RECTYPE);
PUT SKIP LIST(‘ITEM CD :’, DETAIL_REC.ITEM_CD);
END TEST_PROG;
3. ここで最も注意すべき「アライメント(ALIGN)」の罠
PL/Iコンパイラは、デフォルトで `ALIGNED` 属性を想定することが多い。もし、`DEFINED` を使って構造体を再定義する際、基底側の変数と再定義側の変数の間で「境界アライメント」がずれるとどうなるか。
1. コンパイラによるパディング: 意図しない領域にコンパイラが埋め草(パディング)を挿入し、メモリレイアウトが崩壊する。
2. S0C7/S0C4の誘発: `FIXED BIN` や `FLOAT` などの数値型は、本来そのデータ型の境界(境界アライメント)に配置される必要がある。ここを無理やり `POS()` でずらすと、計算時にアライメント例外が発生する。
現場の鉄則:
複雑なメモリオーバーレイを行う際は、必ず `UNALIGNED` 属性を明示すること。
/i
DCL 1 HEADER_REC DEFINED(WORK_AREA) POS(1) UNALIGNED,
3 RECTYPE CHAR(2),
3 SEQ_NUM FIXED BIN(15);
こう書くことで、「コンパイラによる勝手な位置合わせ」を封じ、宣言通りのメモリ配置を強制できる。これはVSAMのコピーブックをそのまま構造体にマッピングする際の定石だ。
4. ONユニットとの組み合わせ:防御的プログラミング
`DEFINED` を使った複雑なデータ構造を扱う際、万が一のアドレス不正は致命的だ。`ON AREA` や `ON STORAGE` を活用するのも一つの手だが、基本は「アクセス前に長さをチェックする」こと。
/i
/ 例: 読み込んだレコード長が最低限の長さを持っているか検証 /
ON ERROR BEGIN;
PUT SKIP LIST(‘データ定義不正によるメモリアクセスエラー発生’);
CALL PLIDUMP(‘TBC’); / 状況をダンプに吐き出す /
STOP;
END;
ベテランからのアドバイス
`DEFINED` は非常に強力だが、多用すると「コードを読んだだけでは、どの変数がどのメモリを指しているか追えない」という保守性の地獄に陥る。
- コメントを惜しむな: どこを `POS()` でずらしているのか、そのオフセット値の根拠を必ずコメントに残せ。
- 構造体は再利用せよ: `DEFINED` を散らばらせず、INCLUDE文で定義を一箇所に集約しろ。
PL/Iのメモリ管理は、ハードウェアの挙動を直接制御できる最後の砦のようなものだ。この「機械に近い感覚」を大切にしながら、堅牢なシステムを構築していってほしい。
何かあれば、またいつでも相談に来い。バッチのログを見れば、大抵のバグの居場所はわかるものさ。
