こんにちは!IBMメインフレームの世界へようこそ。システムアーキテクトの私です。
JavaやCOBOLといったモダン、あるいは従来型のビジネス言語をバリバリ書いてきた方にとって、PL/I(Programming Language One)という名前を聞くだけで、「なんだか古めかしくて難しそう……」「ポインタとかメモリ直接操作って言われると、C言語の悪夢が蘇る……」なんて、身構えてしまうかもしれませんよね。
でも、どうか安心してください。怖がる必要は全くありません。
今回は、PL/Iの数ある強力な武器の中でも、特にエンジニアのロマンをくすぐる「BASED属性とポインタ変数によるメモリ直接操作」について、実務の現場の泥臭い話を交えながら、優しく紐解いていきたいと思います。
Javaのようにガベージコレクションがすべてを綺麗に片付けてくれる世界や、COBOLのようにレコードレイアウトがガチガチに守ってくれる安全な世界とはひと味違う、「メモリの荒野をダイレクトに駆け抜けるスリルと快感」を一緒に味わってみましょう!
—
1. 他言語出身者が驚く、PL/Iの「ポインタ」と「BASED属性」の正体
Javaにはポインタという概念はありませんし(参照はありますが隠蔽されています)、COBOLにはそもそもメモリアドレスを直接いじるような危険な構文はありません。
しかし、PL/Iには最初からこれらが備わっています。ここで登場するのが、ADDR関数とBASED属性です。
「BASED属性」ってなに?(アパートの例え)
JavaのオブジェクトやCOBOLのデータ項目は、「ここに箱を作ります!」と宣言した瞬間にメモリが確保されますよね。これをPL/Iで例えるなら、「土地を買って家を建てる」ようなものです。
一方で、BASED属性というのは、言わば「住所(ポインタ)だけを先に決めておいて、家自体はその時々で自由に出し入れする」ようなものです。
1
/ ポインタ変数の宣言 /
DCL P_CUST_PTR PTR;
/ BASED属性がついた構造体(これ自体はまだメモリ上の実体を持っていません!) /
DCL 1 CUST_REC BASED(P_CUST_PTR),
5 CUST_ID CHAR(5),
5 CUST_NAME CHAR(30);
この `CUST_REC` は、単なる「間取り図(テンプレート)」です。このままではメモリ上に実体はありません。ここに `P_CUST_PTR` という「住所(メモディのアドレス)」を教えてあげることで、初めてその場所にあるデータを読み書きできるようになるんです。
—
2. ADR関数とADDR関数:メモリの海を覗き見る窓
メモリを直接操作するときによく使うのが、ADDR関数とADR関数です。ここ、初学者が一番混乱しやすいポイントなので、丁寧に整理しますね。
- `ADDR(変数)`:
その変数がメモリ上のどこにいるか(ポインタ値)を返してくれます。Javaの `hashCode()` やCの `&operator` のようなものです。
- `ADR(式)`:
こちらはビンテージなPL/Iコードや、古いマイグレーション案件でよく見かける組み込み関数です。アドレスの数値を直接取得したり、計算したりする際に使われます。
百聞は一見にしかず。実際にメモリを直接指し示してデータを読み取るサンプルコードを見てみましょう。大文字ベースの、まさにメインフレームらしい書き方です。
1
/ ========================================================== /
/ BASED属性とADDR関数を用いたメモリ直接操作のサンプル /
/ ========================================================== /
TEST_PROG: PROC OPTIONS(MAIN);
/ 1. ワーキング用のバッファ(メモリ上の実体)を確保 /
Dcl WORK_BUFFER CHAR(100) INIT(‘0123456789ABCDEF’);
/ 2. アドレスを格納するポインタ変数 /
Dcl MY_PTR PTR;
/ 3. BASED属性を持つマッピング用の構造体定義 /
Dcl 1 MAP_AREA BASED(MY_PTR),
3 HEADER CHAR(5),
3 BODY CHAR(10);
/ — 処理開始 — /
/ WORK_BUFFERの先頭アドレスをポインタに設定する /
MY_PTR = ADDR(WORK_BUFFER);
/ MAP_AREAを通じて、WORK_BUFFERのメモリを直接覗き見る /
PUT SKIP LIST (‘HEADERの内容: ‘ || HEADER);
PUT SKIP LIST (‘BODYの内容: ‘ || BODY);
END TEST_PROG;
【実行するとどうなるか?】
`WORK_BUFFER` に格納された `’0123456789ABCDEF’` という文字列に対し、`MAP_AREA` という「型枠」をパコッとはめ込んでいます。
結果として、`HEADER` には先頭5バイトの `’01234’` が、`BODY` にはその次の10バイトの `’456789ABCD’` が(※文字数の切り貼りはイメージです)、そのままメモリ上から直接スライスされて表示されます。ファイルレイアウトの解析や、電文バッファのアンパック処理などで、このテクニックがよく使われます。
—
3. ここがプロの腕の見せ所!「アライメント違反」という見えない罠
さて、ここからがレガシーシステムのアーキテクトとしての本領発揮です。
メモリを直接操作できるということは、「ハードウェアの機嫌を損ねるリスク」と隣り合わせであることを意味します。それがアライメント違反(Alignment Violation)です。
アライメントってなに?
IBMのメインフレーム(System/390やz/Architecture)は、データをメモリから読み書きする際、「2の倍数」「4の倍数」「8の倍数」といった特定のメモリアドレスの境界(Boundary)にデータがきれいに収まっていることを好みます。
- フルワード(4バイトの数値など)は、4で割り切れるアドレスにいてほしい。
- ダブルワード(8バイトの浮動小数点やポインタなど)は、8で割り切れるアドレスにいてほしい。
もし、あなたがBASED属性を使って、奇数番地などの「中途半端なアドレス」に数値データを無理やり割り当てたとしましょう。どうなるでしょうか?
【綺麗なアライメント(4バイト境界)】
アドレス: 0x0004 | 0x0005 | 0x0006 | 0x0007 | -> 一発で読み込める!快感!
【アライメント違反(奇数番地スタート)】
アドレス: 0x0003 | 0x0004 | 0x0005 | 0x0006 | -> 跨いでしまうのでCPUが2回アクセス必要…遅い!
ハードウェアは親切なので、奇数番地から始まっていても頑張ってデータを読み取ってくれます。しかし、「本来1回のメモリフェッチで済むところを2回に分けて読み込む(あるいは例外が発生する)」ため、システム全体として凄まじい性能低下(パフォーマンス・デグラデーション)を引き起こします。これがバッチ処理の長時間化の隠れた原因になっていることが、実務では本当によくあるのです。
対策:ALIGNED属性を使いこなそう
PL/Iでは、このアライメントを制御するために ALIGNED属性 と UNALIGNED属性 が用意されています。
- `ALIGNED`: メモリの境界にきれいに揃える(パフォーマンス重視。ただし隙間にパディング(余白)が入ることがある)。
- `UNALIGNED`: 隙間なく詰める(COBOLの `SYNCなし` やストレージ節約重視。ただし奇数番地に当たると性能リスクあり)。
BASED属性で外部から受け取った生ポインタのデータをマッピングする際は、ハードウェアのアーキテクチャを意識して、適切に `ALIGNED` を指定するか、コンパイラオプション(`ALIGN` など)の挙動を把握しておくことが、シニア・アーキテクトとしての必須スキルとなります。
—
4. まとめ:怖がらずに、しかし敬意を持ってメモリと向き合おう
いかがでしたでしょうか?
PL/Iの `BASED` 属性や `ADDR` 関数、そしてアライメントの話。最初は「なんだか難しそう」と思ったかもしれませんが、要するに「コンピュータのメモリ構造をダイレクトにハックするための強力な道具」です。
JavaやCOBOLの安全な枠組みに慣れていると最初はドキッとするかもしれませんが、メインフレームのハードウェア特性を理解し、きれいにメモリを整えてあげたときのPL/Iプログラムは、驚くほど高速に、美しく動作します。
マイグレーションの調査やレガシーコードの改修で、もし奇妙なポインタ操作に出会ったら、「おっ、メモリの荒野を直走りしているな」と、ぜひ心の中でニヤリとしながら、一つずつ紐解いていってくださいね。
それでは、次回のメインフレーム・アーキテクチャ講座でお会いしましょう!
