PL/Iの「DEFINED」属性:メモリを操る劇薬と、その正しい作法
現場で長年メインフレームと対峙していると、後輩から時折こんな質問を受ける。「なぜ古いプログラムには、同じメモリ領域を別の名前で呼ぶような、一見すると不可解なコードが多いのか?」と。
答えはシンプルだ。ストレージが現代のように潤沢ではなかった時代の名残であり、同時に、限られたメモリ空間で最大のパフォーマンスを叩き出すための「職人技」だったからだ。今日はその中心にある、PL/Iの `DEFINED` 属性について、実務的な視点から深掘りしていこう。
1. 予約語なき言語、PL/Iの矜持
まず前提として、PL/Iには「予約語」という概念が存在しない。`IF` も `THEN` も、極端な話をすれば変数名として使えてしまう(もちろん推奨はしないが)。この柔軟性が、実は `DEFINED` 属性を扱う際の「読み解き」を難しくしている要因でもある。
`DEFINED` は、単なる別名定義ではない。ある変数のメモリ位置を、別の変数のメモリ位置に「重ね合わせる」魔法だ。しかし、この魔法は往々にして「アライメント違反」という、デバッグ泣かせの怪物を引き連れてくる。
2. DEFINED属性とメモリ再定義の仕組み
`DEFINED` を使うと、COBOLの `REDEFINES` に似た動作を実現できるが、PL/Iの場合はより柔軟で、かつ危険だ。
/i
DCL BUFFER CHAR(100);
DCL WORK_AREA CHAR(100) DEFINED(BUFFER); / BUFFERとメモリを共有 /
DCL 1 HEADER_REC DEFINED(BUFFER), / 構造体をBUFFERに重ねる /
3 TYPE CHAR(1),
3 LENGTH BIN FIXED(15);
この例では、`BUFFER` という単なる文字配列に、`HEADER_REC` という構造体の定義を被せている。これにより、バイナリデータを受信した際、いちいち `SUBSTR` で切り出す手間を省き、構造体経由でスマートに値を取り出せるわけだ。
3. 現場が恐れる「アライメント違反」の正体
ここで注意が必要なのは、CPUがメモリ上のデータを読み込む際の「境界」だ。例えば、4バイトのフルワード(`BIN FIXED(31)` など)は、4の倍数のアドレスから始まっている必要がある。
もし `DEFINED` を使って、奇数番地から無理やりフルワードを読み込もうとすれば、システムは容赦なく「アライメント例外」を吐いて異常終了(S0C4やS0C6)する。
特に、VSAMから読み込んだレコードを `DEFINED` でマッピングする際は要注意だ。レコードの途中で型を切り替える際、意図せず奇数アドレスにデータが配置されないよう、`ALIGNED` 属性を明示的に指定する癖をつけてほしい。
4. 実践的なコード例:VSAMレコードの動的解析
現場のバッチプログラムでよく遭遇する、レコードタイプによって中身が変わるパターンを例示しよう。
/i
/ VSAMから読み込んだレコードを効率的に扱う例 /
DCL IN_REC CHAR(256);
/ レコードの先頭に構造体を重ねる /
DCL 1 COMMON_HEADER DEFINED(IN_REC),
3 REC_TYPE CHAR(1),
3 REC_LEN BIN FIXED(15);
/ 後半を別構造体で定義(アライメントに注意) /
DCL 1 DATA_BODY DEFINED(IN_REC),
3 FILLER CHAR(4),
3 DATA_VAL BIN FIXED(31) ALIGNED;
/ ONユニットによる例外処理の基本 /
ON CONDITION(SUBSCR_ERR) BEGIN;
PUT SKIP LIST(‘範囲外アクセスを検知しました’);
CALL ABEND_PROC;
END;
/ 処理本体 /
READ FILE(VSAM_FILE) INTO(IN_REC);
IF COMMON_HEADER.REC_TYPE = ‘A’ THEN DO;
/ ここでDATA_BODYを参照する際はアライメントに留意 /
PUT SKIP LIST(‘VALUE IS:’, DATA_BODY.DATA_VAL);
END;
5. ベテランからのアドバイス:デバッグのコツ
`DEFINED` を使ったコードを修正する際、以下の3点を必ず確認してほしい。
1. 境界の計算を怠らない: `DEFINED` で指定する変数のサイズが、元のメモリ領域を超えていないか?(コンパイラは警告を出さないことも多い)。
2. `ALIGNED` / `UNALIGNED` の不一致: 構造体同士の `DEFINED` では、必ず両者の属性を合わせろ。片方が `ALIGNED` で片方が `UNALIGNED` だと、オフセットが微妙にズレて地獄を見る。
3. BUILTIN関数の活用: `ADDR(変数値)` や `STORAGE(変数)` を `PUT SKIP LIST` で出力し、実際にメモリ上でどの位置を指しているかを確認するデバッグルーチンを埋め込む勇気を持て。
PL/Iは、機械語に近い制御を可能にする強力な言語だ。`DEFINED` を使いこなすことは、システムの「深層」にアクセスすることに他ならない。一見レガシーな技術に見えるかもしれないが、このメモリ制御の感覚は、現代のどの言語を扱う上でも、君のエンジニアとしての血肉になるはずだ。
迷ったら、仕様書よりも「メモリマップ」を信じろ。それが、この過酷なメインフレームの世界で生き残るための、唯一の定石だ。
