こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいは従来型のビジネス言語をバリバリ書いてきた方にとって、PL/I(ピーエルアイ)という名前を聞くだけで「なんだか難解そう」「古いレガシーの呪文みたい……」と身構えてしまうかもしれませんよね。でも、安心してください。一つひとつの仕様を紐解いていけば、PL/Iがいかに合理的で、当時のエンジニアが考え抜いた優れた言語であるかが分かってきます。
さて、今回はPL/Iの数ある強力な武器の中でも、少しマニアックだけど実はとても面白い「OFFSET属性(相対アドレス指定)」をテーマに取り上げます。
「ポインタならC言語やJava(参照という形ですが)で知ってるよ!」という方こそ、PL/IのポインタとOFFSETの世界を覗くと、「おぉ、こうなっているのか!」と膝を叩くはずです。一緒に優しく紐解いていきましょうね。
—
そもそもPL/Iの「ポインタ」と「OFFSET」って何が違うの?
JavaやC言語を経験されている方なら、メモリ上のアドレスを指し示す「ポインタ」の概念はお馴染みですよね。C言語では `int p;` のように書きます。
PL/Iにもズバリ `POINTER` というデータ属性があります。しかし、メインフレーム(IBM Z)の世界では、31ビットや64ビットの絶対アドレス(メモリの番地そのもの)をそのままファイルに書き出したり、別のプログラムにそのまま渡したりすると、アドレスが変わってしまって大惨事になることがありますよね。
そこで登場するのが、今回主役の「OFFSET属性」です。
イメージしやすい例え:社内便の「フロア番号」と「席番」
想像してみてください。
- POINTER(ポインタ型):東京の本社ビルからの「絶対的な緯度・経度(あるいは絶対番地)」のようなものです。ビルが引っ越したら使えなくなっちゃいますよね。
- OFFSET(オフセット型):ある特定の「部屋(AREA)」の入り口からの「何歩目か」という相対的な距離です。
この「特定の部屋」にあたるのが、PL/Iの `AREA`(エリア) というデータ構造です。OFFSET変数は、常にこの `AREA` の先頭からの相対位置(オフセット)を保持します。だから、その `AREA` ごとデータをディスクに保存(PDSやVSAMへの格納)しても、別のメモリ領域にロードし直しても、相対的な位置関係は絶対に狂わないという強力なメリットがあるのです。
—
AREAとOFFSETの基本コンビネーション
百聞は一見にしかず。実際にPL/Iでどのように書くのか、コードを見てみましょう。実務のマイグレーション調査でもよく見かける、典型的な構造です。
1
/ ————————————————せ /
/ AREAとOFFSETを使ったリスト構造のサンプルコード /
/ ————————————————す /
TEST_PROG: PROC OPTIONS(MAIN);
/ 1. 可変領域を管理するAREAの宣言(サイズは32KB) /
DCL MY_AREA AREA(32768) BASED(AREA_PTR);
DCL AREA_PTR POINTER;
/ 2. リスト要素の構造体定義 /
DCL 1 ELEMENT BASED(ELEM_PTR),
2 NEXT_ELEM OFFSET(MY_AREA), / 次の要素へのオフセット(相対位置) /
2 DATA_VAL CHAR(10); / 実際のデータ /
DCL ELEM_PTR POINTER;
DCL HEAD_OFF OFFSET(MY_AREA); / 先頭要素へのオフセット /
/ 実際の処理ロジック(イメージ) /
/ ※実際にはALLOCATE文でAREA内にメモリを確保して使います /
DISPLAY(‘OFFSET属性の基本理解完了です!’);
END TEST_PROG;
なんだか見慣れないキーワードが並んでいてドキドキしましたか?
大丈夫です。ポイントは `OFFSET(MY_AREA)` の部分です。この `NEXT_ELEM` という変数は、絶対的なメモリ番地ではなく、「`MY_AREA`という名の巨大な箱のどこに次のデータがあるか」という相対距離だけを覚えています。
—
ポインタとOFFSETの華麗なる変換(キャスト)
実務でPL/Iのコードを読んでいると、「あれ? 今ポインタをいじってたのに、突然OFFSETに代入しているぞ?」という場面に出くわします。
PL/Iは、ポインタとオフセットを相互に変換するための組み込み関数(ビルトイン関数)を用意してくれています。これが非常に直感的で分かりやすいんです。
1. ポインタからOFFSETへ変換する場合
「今、メモリのこの絶対位置(POINTER)にあるんだけど、それをAREA内の相対位置に直したいな」という時は、`OFFSET` 関数を使います。
1
/ ポインタ変数を、AREA基準のオフセット値に変換する /
ELEM_OFF = OFFSET(ELEM_PTR, MY_AREA);
- 「`ELEM_PTR` が指す場所は、`MY_AREA` の先頭から見ると何バイト目かな?」を自動計算してオフセット変数に詰めてくれます。
2. OFFSETからポインタへ変換する場合
逆に、「ファイルの保存領域から読み込んだオフセット値から、実際のメモリアクセス用のポインタを作りたいな」という時は、`POINTER` 関数を使います。
1
/ オフセット値から、実際のメモリアクセス用ポインタを復元する /
ELEM_PTR = POINTER(ELEM_OFF, MY_AREA);
- 「`MY_AREA` の先頭から `ELEM_OFF` 分だけ進んだ場所の、実際のメモリ番地を教えて!」という逆引きですね。
これらを組み合わせることで、「メモリ上ではバラバラになりがちな複雑なリスト構造を、一つの大きな塊(AREA)としてキレイに管理し、丸ごとファイル入出力する」という、レガシーシステムならではの高度でエレガントなデータ処理が実現できるのです。
—
初学者がハマりやすい注意点とアドバイス
最後に、実務の現場や移行プロジェクトで「うわっ」となりやすいポイントを、先輩風を吹かせてこっそりお伝えしておきますね。
1. AREAの範囲外を参照しちゃうエラー(IBMサブコード)
OFFSETはあくまで「そのAREAの中での相対位置」です。もし誤ったオフセット値を指定して `POINTER` 関数で実アドレスに変換しようとすると、AREAの境界をはみ出してしまい、お馴染みのコンバットエラー(SOC4系などの例外)やPL/I実行時エラーを引き起こします。「OFFSETを使うときは、必ず親であるAREAの大きさとセットで意識する」これが鉄則です。
2. 他言語からの移行時の誤解
JavaやCOBOLしかやったことがないと、「なぜわざわざこんなまらわしい相対位置にする必要があるんだ?」と思われるかもしれません。しかし、メインフレームのバッチ処理では、大量のデータを一括して外部記憶装置(DASD)にスナップショット的に書き出す要件が多々あります。その際、絶対ポインタでは再ロード時に使い物にならないため、このOFFSET属性が唯一無二の救世主となるのです。
—
まとめ
いかがでしたでしょうか?
PL/Iの `OFFSET` 属性、そして `AREA` とのコンビネーション。最初は見慣れない構文に驚いたかもしれませんが、「特定の箱の中での位置を指す相対座標」だと捉えれば、とってもシンプルで理にかなった仕組みだと言えませんか?
レガシーシステムのソースコードを読むときは、一見難解な記述であっても、当時のプログラマが「限られたリソースとメモリをどう安全に、効率よく扱うか」を悩んだ末の知恵がつまっています。
「怖くないですよ、一つずつ紐解けば簡単です」。
この調子で、メインフレームの世界をどんどん楽しく攻略していきましょう!次回の解説もお楽しみに!
