こんにちは!メインフレームの荒波にもまれながら、日々PL/Iのコードと格闘されているエンジニアの皆さん、あるいは「JavaやCOBOLなら知っているけれど、急にPL/Iを任されてしまった……」と冷や汗をかいている初学者の皆さん、ようこそいらっしゃいました。
レガシーシステムの世界へ足を踏み入れると、現代の言語にはない独特な仕様に出会ってギョッとしてしまうこと、ありますよね。「なんだこの暗号は!」と画面の前でフリーズしたくなる気持ち、痛いほどよく分かります。でも、大丈夫ですよ。一つひとつの仕様にはちゃーんと歴史的な理由や、メインフレームならではの泥臭い(けれど美しい)工夫が隠されているんです。
今回は、そんなPL/Iの奥深い世界から、「OFFSET関数とPOINTER関数の相互変換」、そして背後にある「エリア(Area)管理」という、ちょっとマニアックだけど避けて通れないテーマを、優しく紐解いていきたいと思います。
Javaの参照やCOBOLのポインタ(のようなもの)とは一味違うPL/Iの世界へ、肩の力を抜いて出発しましょう!
—
そもそも「ポインタ」と「オフセット」って何が違うの?
Javaしか触ったことがない方にとって、「メモリの番地(アドレス)を直接いじる」なんて聞くだけで、ゾンビが出てきそうなホラーホスピタルを想像しちゃうかもしれません。COBOLでも、最近のバージョンではADDRESS OFなんて使いますが、基幹系の古いコードだとあまりお目にかからないかもしれませんね。
PL/Iには、メモリの居場所を示すために2種類の武器が用意されています。
1. POINTER(ポインタ)型
- メモリ上の「絶対番地(ここからここまでがこのデータの居場所です!という純粋な住所)」を指し示すものです。
2. OFFSET(オフセット)型
- ある「基準地(エリアの先頭)」から数えて、「何バイト目にあるか」という相対的な距離(道のり)を示すものです。
なぜわざわざ「オフセット」なんて面倒なものがあるの?
「絶対住所だけでいいじゃん!」って思いますよね。実はここにメインフレームならではの涙ぐましい事情があります。
メインフレームの世界では、大量のデータを磁気テープやファイルに一度「ガバッ」と書き出して、また別のプログラムで読み込む、なんてことを日常茶飯事に行います。
もし、プログラムAがメモリ上の「番地X」にデータを展開して、その中のポインタが「番地Y」を指していたとしましょう。それをそのままファイルに保存し、別の日の夜間バッチでプログラムBが読み込んだとき……同じ「番地Y」に、さっきと同じデータが居る保証はどこにもありませんよね?
そう、絶対アドレスは、プログラムの実行が終わったり、メモリ空間が変わったりすると、一瞬でゴミクズになってしまうのです。
そこで登場するのがOFFSET(オフセット)です。
「エリアの先頭から〇バイト目」という相対位置であれば、データをファイルに保存して別の場所にロードし直しても、値が狂うことはありません。この「ファイル保存も怖くない可搬性」を実現するために、PL/IにはOFFSET関数とPOINTER関数が用意されているんです。
—
主役たちの登場:Area(エリア)という名の「砂場」
OFFSETを語る上で絶対に外せないのが `AREA` 属性 です。
PL/Iでは、動的にメモリを確保する際、あらかじめ「この広さのメモリブロック(砂場)の中でやりくりしてね」という領域を宣言します。これがAreaです。
1
/ 10000バイトの専用砂場(Area)を宣言します /
DCL MY_AREA AREA(10000) BASED;
この砂場(Area)の中では、変数が自由に作っては消され、ポインタが飛び交います。そして、この砂場の中での位置を記録するためにOFFSET変数が使われるわけです。
—
実践!OFFSETとPOINTERを華麗に行き来するコード
百聞は一見に如かず。実際にコードを見てみましょう。
Javaでいうオブジェクトの自己参照リスト(リンクリスト)のようなものを、PL/IのBased変数とAreaを使って表現してみます。
1
/ ========================================================== /
/ OFFSETとPOINTERの相互変換サンプルプログラム /
/ ========================================================== /
TEST_CONVERT: PROC OPTIONS(MAIN);
/ 1. リスト要素の構造体を定義します(Based変数) /
Dcl 1 NODE Based,
2 NEXT_OFF OFFSET(MY_AREA), / 次の要素へのオフセット(砂場内での距離) /
2 DATA_VAL FIXED BIN(31); / データ本体 /
/ 2. 10KBのメモリ領域(エリア)を定義 /
Dcl MY_AREA AREA(10000) BASED(P_AREA);
/ 3. 作業用ポインタとオフセットの宣言 /
Dcl P_AREA POINTER;
Dcl P_NODE POINTER;
Dcl O_NODE OFFSET(MY_AREA);
Dcl I FIXED BIN(31);
/ 4. エリア自体のメモリをヒープから取得して初期化 /
ALLOCATE MY_AREA;
/ エリアの中をゼロクリアして初期化します /
MY_AREA = ”;
/ ====================================================== /
/ POINTER から OFFSET への変換(ADDR/OFFSET関数) /
/ ====================================================== /
/ エリア内に新しいNODE用の領域を確保します /
ALLOCATE NODE IN(MY_AREA) SET(P_NODE);
/ データを設定 /
P_NODE->DATA_VAL = 100;
P_NODE->NEXT_OFF = NULL; / ま後ろには誰もいないのでヌル /
/ ★ ここがポイント!絶対アドレス(P_NODE)を、エリア基準のオフセットに変換します /
O_NODE = OFFSET(P_NODE, MY_AREA);
PUT SKIP EDIT (‘絶対アドレスをオフセットに変換しました。距離 = ‘, O_NODE)
(A, F(10));
/ ====================================================== /
/ OFFSET から POINTER への変換(POINTER関数) /
/ ====================================================== /
/ ★ ここもポイント!ファイルから読み込んだりした「オフセット(O_NODE)」から、
現在の環境での絶対アドレス(P_NODE)を復元します! /
P_NODE = POINTER(O_NODE, MY_AREA);
PUT SKIP EDIT (‘オフセットから絶対アドレスを復元!中のデータ = ‘, P_NODE->DATA_VAL)
(A, F(10));
/ 後片付け /
FREE MY_AREA;
END TEST_CONVERT;
コードの解説:ここが怖くないポイント!
1. `OFFSET(変数, エリア名)` 関数
- 「いまこの変数がいる絶対アドレス(`P_NODE`)」を、「指定したエリア(`MY_AREA`)の先頭から何バイト目か」という数値にギュッと縮めて変換してくれます。
2. `POINTER(オフセット変数, エリア名)` 関数
- 「エリア内のこの位置(`O_NODE`)」と、「現在のメモリ上にあるエリアの先頭位置(`MY_AREA`)」を合体させて、「あ、今のメモリ上ではここにあるのね!」という絶対アドレス(`P_NODE`)を再計算してくれます。
この仕組みがあるおかげで、「メモリ上の絶対位置に依存しないデータ構造」をメモリ上に構築し、それをそのまま外部ファイルにダンプ(書き出し)して、別の日に復元するといった芸れつがレガシーシステムでは可能になっているのです。
—
実務(マイグレーションや保守)での注意点
ここで、現場でありがちな「やらかしポイント」をこっそりシェアしておきますね。
- エリアをまたぐアクセスは御法度!
`MY_AREA` という砂場で作ったオフセットを、全く別の砂場(たとえば `ANOTHER_AREA`)のポインタ計算に使おうとすると、コンパイルエラーになるか、運悪く通ってもメモリアクセス違反(いわゆる“スレド”)でシステムが盛大にアボート(異常終了)します。オフセットは「必ず親であるエリアとセット」で管理しましょう。
- 予約語ではないけれど……
今回のテーマである `OFFSET` や `POINTER` は、PL/Iの言語仕様上はキーワード(予約語)ではありません。つまり、変数名に `DCL OFFSET FIXED BIN;` なんて書くことも理論上は可能です(コンパイラが文脈から判断するため)。でも、そんなことしたら後からコードを読む人が全員泣きを見るので、識別子(変数名)フレンズとしては絶対に避けてくださいね!
—
まとめ
いかがでしたでしょうか?
「OFFSET関数とPOINTER関数の相互変換」、そして「Area管理」のコンビネーション。最初は呪文のように見えたコードも、「データを安全にファイル保存や領域移動させるための、メインフレーム時代の知恵なんだな」と分かると、少しだけ愛おしく感じ……はしないかもしれませんが(笑)、怖さは和らいだのではないでしょうか。
他言語の経験があるあなたなら、PL/Iのデータ構造のロジックはすぐにマスターできるはずです。詰まったときは、焦らず「いま、どの砂場(Area)の話をしているんだっけ?」と立ち返ってみてくださいね。
それでは、次回のレガシー探訪もお楽しみに!快適なメインフレームライフを応援しています!
