こんにちは!メインフレームの世界へようこそ。
JavaやC言語、あるいはCOBOLといったモダンまたは一般的なビジネス言語をバリバリ書いてきた方にとって、IBMメインフレームの「PL/I(ピーエルアイ)」の世界は、最初ちょっとした異世界のように感じられるかもしれません。
特に、メモリ上の番地を直接指し示す「ポインタ」や「アドレス解決」という話を聞いただけで、「うわ、難しそう……落ちたら怖いセグメンテーション違反(S0C4)の予感がする……」と身構えてしまいますよね。
でも、安心してください。怖がる必要は全くありません。
今回は、PL/Iにおけるアドレス操作の要である `ADDR`関数 と `POINTER`関数 が、内部でどのように変位(Displacement)を計算し、変数にたどり着いているのかを、一緒にゆっくり紐解いていきましょう。
—
1. 他言語出身者が驚く、PL/Iの「アドレス」に対するスタンス
Javaの世界では、メモリの番地(アドレス)を直接意識することはほとんどありません。C言語ならポインタ演算子(`&` や “)を使いますが、PL/Iのポインタは、IBMアーキテクチャ(z/Architecture)のハードウェア構造と深く結びついています。
IBMメインフレームのCPUは、メモリを指すときに「ベースレジスタ(基準となる番地)」と「変位(そこから何バイト離れているかというズレ)」というコンビネーションでデータを捉えています。
PL/Iの `ADDR`関数や `POINTER`関数は、このハードウェアの仕組みをプログラマが直接、かつ安全にコントロールするための強力な武器なんです。
—
2. ADDR関数:変数の「現在地」を暴く
まずは `ADDR`(または `ADR`)関数から見ていきましょう。
これは非常にシンプルで、C言語の `&`(アドレス演算子)と同じ役割を果たします。ある変数が、今メインメモリ(ストレージ)のどこに陣取っているのか、その先頭番地(ポインタ)を教えてくれます。
ちょっとイメージしてみましょう
会社組織で例えるなら、`ADDR` は「総務部にいる〇〇さんの座席番号(内線・フロア)」を調べるようなものです。「佐藤くん、今どこに座ってる?」と聞いて、オフィスの見取り図上の位置をピンポイントで教えてもらうイメージですね。
実際のPL/Iコードで見てみましょう。
1
/ ————————————————せ /
/ ADDR関数の基本動作サンプル /
/ ————————————————シン /
DEMO: PROC OPTIONS(MAIN);
DCL MY_VAR CHAR(10) INIT(‘IBM-Z’); / 対象の文字変数 /
DCL P_VAR POINTER; / アドレスを格納するポインタ変数 /
DCL HEXTEXT PIC ‘XXXXXXXX’; / 16進数表示用のワーク /
/ MY_VARのメモリ上のアドレスを取得してポインタに代入 /
P_VAR = ADDR(MY_VAR);
/ アドレスの値を16進数で確認する組み込み関数(HEX)を使用 /
HEXTEXT = HEX(P_VAR);
PUT SKIP LIST(‘MY_VARの格納アドレスは: ‘ || HEXTEXT);
END DEMO;
このコードを実行すると、`MY_VAR` がメモリ上のどの位置に割り当てられているのかが、16進数のアドレスとして取得できます。基幹システムのダンプリスト(ABEND時の解析資料)を見る時、この感覚があると一気に謎が解けるようになりますよ。
—
3. POINTER関数:ベースと変位から「正確な位置」を再構築する
さて、ここからが本題であり、PL/Iが少しユニークで面白いところです。
「ある基準となるアドレス(ベース)」から、「何バイト離れているか(変位・オフセット)」を指定して、別のポインタを作り出したい時はどうすればいいでしょうか?
ここで登場するのが `POINTER`関数(または `PTR`関数)です。
IBMメインフレームの機械語命令(ストレージ・オペランド)は、基本形として `D(X,B)` つまり 「変位(Displacement) + ベースレジスタ(Base)」 という形式でメモリを参照します。PL/Iの `POINTER(base, displacement)` も、まさにこのハードウェアの仕組みをそのまま言語仕様に落とし込んだものです。
例え話:ビルのフロアと机の番号
- ベース(Base): 「第3ビルの3階」という大本の基準位置。
- 変位(Displacement): 「3階の入口から数えて、奥へ42バイト(42歩)進んだ場所」というズレ。
この2つを合算することで、迷うことなく目的の机(データ)にたどり着けますよね。
実務のマイグレーションや、可変長レコード(BDWやRDWを含むレコード構造)を自前でパースする際、この `POINTER`関数がもの凄く役に立ちます。コードで確認してみましょう。
1
/ ————————————————せ /
/ POINTER関数によるベースと変位の計算サンプル /
/ ————————————————シン /
ADDR_CALC: PROC OPTIONS(MAIN);
/ 8バイトの領域を持つダミーの文字変数を用意 /
DCL BASE_AREA CHAR(20) INIT(‘ABCDEFGHIJKLMNOPQRST’);
DCL P_BASE POINTER;
DCL P_TARGET POINTER;
/ ベースとなる変数のアドレスを取得 /
P_BASE = ADDR(BASE_AREA);
/ 【重要】POINTER関数を使って、ベースから「5バイト先」を指すポインタを作る /
/ 第1引数: ベースポインタ, 第2引数: 変位(バイト数) /
P_TARGET = POINTER(P_BASE, 5);
/ 本当に5バイト先(’F’の位置)を指しているか、基底付き変数で確認する /
BEGIN;
DCL TARGET_CHAR CHAR(1) BASED(P_TARGET);
PUT SKIP LIST(‘ベースから5バイト先の文字は: ‘ || TARGET_CHAR);
/ 期待値: ‘F’ (Aから数えて0,1,2,3,4,5番目) /
END;
END ADDR_CALC;
ここで注目してほしいのは、`POINTER(P_BASE, 5)` という記述です。
「ベースアドレスに対して、強制的に5バイト後ろを指し示す新しいアドレスを作り出す」という処理を、コンパイラが安全に(しかも効率的な機械語命令に翻訳して)実行してくれます。
—
4. なぜこの仕組みを知る必要があるのか?(実務でのメリット)
「今時、こんな低レイヤーなアドレス計算をしなくても……」と思われるかもしれません。しかし、長年稼働しているメインフレームの基幹システムでは、以下のような場面に必ず直面します。
1. 巨大な外部バッファや通信電文(エリア)の効率的な切り出し
- コピーブック(構造体)で定義しきれない可変長の部分や、バイナリデータを効率よくスキャンする際、ポインタ演算が必須になります。
2. ストレージのオーバーレイ(領域の重ね合わせ)
- 同じメモリ領域を、ある時は数値として、ある時は文字として見立てたい時、アドレスを共有して型だけをスライドさせるテクニックが使われます。
3. レガシーコードの保守・解析(S0C4エラーの撲滅)
- 「なぜここで変な場所を参照して異常終了(S0C4)するのか?」を突き詰める時、コンパイラが内部でどのようにベースレジスタと変位を計算しているかを知っていると、ダンプリストを見た瞬間に原因(ポインタの指し間違いや領域外参照)がピンと来ます。
—
5. まとめ
PL/Iの `ADDR` と `POINTER` は、一見すると難解な呪文のようですが、本質はとてもシンプルです。
- `ADDR(変数)` = 「今の居場所(住所)を教えて!」
- `POINTER(ベース, 変位)` = 「その住所から、こっちに何歩進んだ場所を新しい住所にして!」
この2つの感覚さえ掴んでしまえば、PL/Iのメモリ制御は怖くありません。むしろ、ハードウェアの動きをダイレクトに感じられる、非常にロジカルで美しい言語だと思えるようになってきますよ。
日々のバッチ改修やマイグレーション調査で、もし複雑なポインタ操作に出くわしたら、今日の「ビルのフロアと歩数」の例えを思い出してみてくださいね。
あなたのメインフレームライフが、少しでも快適で楽しいものになりますように!
