こんにちは!IBMメインフレームの世界へようこそ。システムアーキテクトの私です。
JavaやCOBOLといったモダン、あるいは従来型のビジネス言語をバリバリ書いてきた方にとって、PL/I(Programming Language One)という名前を聞くだけで、「なんだか古臭くて難解そう」「記号だらけで怖そう」と身構えてしまうかもしれませんよね。
でも、安心してください。今日一緒に紐解いていく「CONTROLLED(コントロールド)属性」と、そのスタック管理の仕組みを知れば、「おっ、PL/Iって案外スマートで面白いやつじゃないか」と思っていただけるはずです。
Javaのガベージコレクションや、COBOLの静的なメモリ管理とはひと味違う、PL/Iならではのダイナミックで美しいメモリの「積み上げ・片付け」のドラマを、一緒に覗いてみましょう!
—
そもそも「CONTROLLED変数」ってなに?
Javaしか触ったことがない方なら「オブジェクトの生成(new)」、COBOLなら「WORKING-STORAGE SECTIONでの領域確保」を思い浮かべるでしょう。どちらも変数の寿命やメモリのあり方は、言語の裏側がよしなにやってくれたり、プログラム全体で一つだけだったりしますよね。
しかし、PL/Iの `CONTROLLED` 属性は違います。一言で言うと、「プログラマが自分の意志で、メモリの山(スタック)の上に変数を積んだり下ろしたりできる特等席切符」です。
Javaのローカル変数のようでありながら、もっと手動で、かつ何世代もの「同じ名前の変数」をまるでマトリョーシカ人形のように重ねて管理できるという、ちょっと変態的(褒め言葉です!)でパワフルな機能を持っています。
他の言語との決定的な違い
- COBOL: 変数は基本的に静的(Static)です。プログラムが動いている間、ずっと同じ場所を陣取っています。
- Java: オブジェクトはヒープに作られ、不要になったらJVMが勝手に回収(GC)します。
- PL/I (CONTROLLED): 「今はこの変数の新しい箱がもう一つ欲しい!」と思ったら `ALLOCATE` で積み上げ、「用事が済んだから一番上の箱を捨てるよ」と `FREE` で即座に片付ける。このメモリの「スタック構造(LIFO: Last-In, First-Out)」を、プログラマが完全にコントロールできるのです。
—
ALLOCATEとFREEが生み出す「世代管理」の魔法
百聞は一見に如かず。実際にPL/Iのコードを見てみましょう。大文字で書かれたいかにもメインフレームらしい佇まいですが、日本語コメントを添えてあるので怖くありませんよ。
1
STK01: PROC OPTIONS(MAIN);
/ CONTROLLED属性を持つ変数の宣言 /
/ まだこの時点ではメモリは割り当てられておらず、名前の権利だけがある状態です /
DCL MY_VAR CHAR(10) CONTROLLED;
PUT SKIP LIST(‘— 最初の世代をALLOCATEします —‘);
ALLOCATE MY_VAR;
MY_VAR = ‘第1世代のデータ’;
CALL SHOW_STATUS(‘メインルーチン’);
/ ここで、なんと「同じ名前の変数」をもう一度ALLOCATEしちゃいます! /
PUT SKIP LIST(‘— 同じ変数名をもう一度ALLOCATE(多重化)します —‘);
ALLOCATE MY_VAR;
MY_VAR = ‘第2世代のデータ’;
CALL SHOW_STATUS(‘メインルーチン(2個め)’);
/ 片付け(FREE)の儀式:最後に積んだものから消えていきます /
PUT SKIP LIST(‘— 直近の世代をFREEします —‘);
FREE MY_VAR;
CALL SHOW_STATUS(‘メインルーチン(FREE後)’);
/ 最初の世代もFREEして綺麗に後片付け /
PUT SKIP LIST(‘— 最初の世代もFREEします —‘);
FREE MY_VAR;
END STK01;
/ 現在の変数の状態を表示するサブルーチン /
SHOW_STATUS: PROC(MSG);
DCL MSG CHAR();
DCL MY_VAR CHAR(10) CONTROLLED; / 同じCONTROLLED変数にアクセス /
/
- 面白いことに、ここでは今アクティブな(一番新しく積まれた)
- MY_VAR の値しか見えません!
/
PUT SKIP EDIT (MSG, ‘: 現在見える値 = [‘, MY_VAR, ‘]’) (A,A,A,A);
END SHOW_STATUS;
このコードが実行される頭の中のイメージ
初心者の方が一番混乱しやすいのが、「同じ `MY_VAR` という名前なのに、二回 `ALLOCATE` するとどうなるの?」という点ですよね。
イメージしてください。あなたの机の上に、空のインデックスカード入れ(スタック)があるとします。
1. `ALLOCATE MY_VAR;` を実行(1回目)
- 机の上に新しいカード(第1世代)が置かれ、そこに「第1世代のデータ」と書きます。
2. もう一度 `ALLOCATE MY_VAR;` を実行(2回目)
- なんと、そのカードの上に、さらにピカピカの新しいカード(第2世代)が重ねて置かれます!
- この瞬間、下にあった「第1世代のデータ」は隠れてしまい、普段の操作では見えなくなります(でも消えてはいません)。
3. `MY_VAR = ‘第2世代のデータ’;`
- 今、一番上に露出している第2世代のカードに文字が書き込まれます。
4. `FREE MY_VAR;` を実行
- 一番上にあった第2世代のカードが回収(消滅)されます。
- するとどうでしょう!隠れていた第1世代のカードが再びひょっこり顔を出すのです。これがPL/Iのスタック挙動の真骨頂です。
—
再帰呼び出し(Recursive)との組み合わせで真価を発揮する
「わざわざ同じ変数でそんな面倒なことを……?」と思いましたか?
この `CONTROLLED` 属性とスタック管理が本当に輝くのは、プログラムが自分自身を呼び出す「再帰呼び出し(RECURSIVE)」の場面です。
例えば、階乗計算や、ツリー構造のデータを深さ優先でゴリゴリ探索するバッチプログラムを想像してください。
1
RECUR_SAMPLE: PROC OPTIONS(MAIN);
DCL PROCESS_DATA(N) FIXED BIN(31) CONTROLLED;
/ 再帰的に自分を呼ぶメイン処理のイメージ /
CALL DO_RECURSION(5);
DO_RECURSION: PROC(LEVEL) RECURSIVE;
DCL LEVEL FIXED BIN(31);
/ 再帰呼び出しのたびに、領域が新しくスタックに積み上げられる /
ALLOCATE PROCESS_DATA(LEVEL);
PROCESS_DATA = LEVEL 10;
PUT SKIP LIST(‘レベル:’, LEVEL, ‘ 確保された配列の要素数:’, HBOUND(PROCESS_DATA, 1));
IF LEVEL > 1 THEN
CALL DO_RECURSION(LEVEL – 1); / 自分を呼ぶ /
/ 呼び出しから戻るときに、自分が積んだメモリをきれいに解放する /
FREE PROCESS_DATA;
END DO_RECURSION;
END RECUR_SAMPLE;
再帰呼び出しの深さに応じて、必要なサイズの配列やワークエリアを動的に `ALLOCATE` し、関数を抜けるときに `FREE` する。これにより、静的な領域確保では「最大どれくらいの深さになるか分からないから、大きめに固定領域を取っておくか……(そして領域枯渇エラーに怯える)」というメインフレームあるあるの悪夢から解放されます。
—
実務のマイグレーション現場から:知っておくべき注意点
さて、ここまで読んで「なるほど、便利そうだな」と感じていただけたかと思いますが、現代のオープン系言語(JavaやC#など)に慣れたエンジニアが、レガシーなPL/Iコードの改修やマイグレーション(JavaやC言語へのコンバージョンなど)を行う際、いくつかハマりやすいポイントがあります。最後にシニアアーキテクトからの実務的なアドバイスをいくつか授けましょう。
1. ポインタと混同しないこと
PL/Iには `POINTER` 型を使った `BASED` 属性という、C言語のポインタに近いメモリ管理手法もあります。`CONTROLLED` はポインタを明示的に操作する必要がなく、言語側がスタック構造をよしなに管理してくれる「安全な動的割当て」です。まずは `CONTROLLED` のLIFO挙動をしっかり押さえましょう。
2. FREEの順番を間違えない
スタックですから、積んだ順番と逆の順番(LIFO)で `FREE` するのが基本です。もし世代の管理を誤って意図しないタイミングで `FREE` してしまうと、予期せぬデータ破損やコンパイラによる異常終了(ABEND)を引き起こします。メインフレームのログ(SYSOUT)で `IBM0221S` などのストレージ系エラーが出たら、大抵このアロケーションのミスマッチが原因です。
3. モジュール間を跨ぐときのスコープに注意
`CONTROLLED` 変数は、宣言されたブロックやその下位ルーチンで共有されますが、外部サブプログラムへ渡す際の挙動は少しクセがあります。基本的には「自分を呼んだ側が持っている現世代のインスタンス」が参照されますが、スパゲッティになった古いレガシーコードでこれを乱用すると、どこでメモリが積まれてどこで消えているのか追うのが大変になります。改修時はコールツリーを必ず確認しましょう。
—
おわりに
いかがでしたでしょうか?
PL/Iの `CONTROLLED` 属性と `ALLOCATE`/`FREE` によるスタック管理。一見するとレガシーで取っつきにくく感じられたかもしれませんが、その裏側にあるのは非常にシンプルで合理的な「世代ごとのLIFOスタック」という仕組みです。
「古い言語だから難解だ」と恐れる必要は一切ありません。一つひとつのキーワードの挙動を、こうして身近な例えに置き換えて紐解いていけば、PL/Iはあなたの強力な味方になってくれます。
基幹システムのメンテナンスやマイグレーション調査でこの構文に出会ったら、「あぁ、ここでマトリョーシカみたいにメモリを積んでるんだな」と、ぜひ心の中でニヤリと微笑んでみてくださいね。それでは、次回のレガシーアーキテクチャ講座でお会いしましょう!
