【入門編】STORAGE属性(AUTOMATIC vs STATIC)のメモリ配置 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは!IBMメインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはビジネスで広く使われている言語のご経験がある方にとって、PL/I(Programming Language One)という名前を聞くだけで「なんだか難解そう」「古いレガシーの世界だ…」と身構えてしまうかもしれませんよね。

でも、どうぞご安心ください。怖がる必要は全くありません。
今回は、PL/Iのメモリ管理の心臓部である「STORAGE属性(AUTOMATICとSTATIC)」について、JavaやCOBOLのメモリの仕組みと対比させながら、そっと手を取り合って紐解いていきましょう。

1. JavaやCOBOLの頭で考えるとハマる?PL/Iのメモリの基本

私たちが普段書くプログラムでは、変数を宣言すると、コンピュータのメモリ上のどこかにその置き場所が確保されます。

  • Javaの場合: オブジェクトの多くはヒープ領域に作られ、不要になったらガベージコレクタ(GC)が勝手に回収してくれますよね。
  • COBOLの場合: `WORKING-STORAGE SECTION` に書いたデータは、プログラムが動いている間はずっと同じ場所にドンと居座り続けます。

では、PL/Iはどうでしょう?
PL/Iには、変数がメモリ上のどこに、どのような寿命で存在するのかをプログラマが明示的に(あるいはデフォルトで)指定するためのSTORAGE属性という仕組みがあります。

その中でも特に重要なのが、今回取り上げる `AUTOMATIC`(自動割当て)`STATIC`(静的割当て) です。この2つの違いをしっかり押さえることが、メインフレームのバッチプログラムやオンラインプログラムを安全に読み解く第一歩になります。

2. STATIC と AUTOMATIC:それぞれの正体とイメージ

まずは、それぞれの属性がメモリ上でどのように振る舞うのか、身近な例えでイメージしてみましょう。

STATIC(静的領域):いつも同じ場所にある「マイデスクの引き出し」

`STATIC` は、プログラムが実行されている間、ずっと同じメモリのアドレスを占有し続ける変数です。
Javaでいう `static` 変数、COBOLの `WORKING-STORAGE` にあるデータに近いイメージですね。

  • 特徴: プログラムが始まるときに一度だけメモリが確保され、プログラムが終わるまでその中身は保持され続けます。次に同じルーチンを通ったときも、前回書き換えた値がそのまま残っています。

AUTOMATIC(スタック領域):必要なときだけ組み立てる「折りたたみ式の机」

PL/Iで特にユニーク(そして初心者が一番戸惑うポイント)なのが、この `AUTOMATIC` です。実は、PL/Iでは何も明示的に指定しない場合、ローカル変数はデフォルトで `AUTOMATIC` になります。

Javaのメソッド内で宣言するローカル変数や、C言語の自動変数と同じように、スタック領域に確保されます。

  • 特徴: そのブロック(プロシージャやBEGINブロック)に入った瞬間にメモリがニョキッと生まれ、そのブロックを抜けるときには綺麗さっぱり消滅(解放)します。次にそのブロックに入ったときには、以前の値は残っていません(初期値は保証されないか、言語仕様上の規定値になります)。

3. 百聞は一見にしかず!実際のPL/Iコードを見てみよう

それでは、実際のPL/Iコードでその動きを確認してみましょう。大文字で書かれたレガシーな雰囲気漂うコードですが、日本語のコメントを丁寧に添えましたので、一緒に見ていきましょう。

1
STGEX: PROC OPTIONS(MAIN);

/ ————————————————– /
/ メイン処理ブロック /
/ ————————————————– /
PUT SKIP LIST(‘— 1回目のルーチン呼び出し —‘);
CALL DEMO_ROUTINE;

PUT SKIP LIST(‘— 2回目のルーチン呼び出し —‘);
CALL DEMO_ROUTINE;

END STGEX;

/ ————————————————– /
/ デモ用サブプロシージャ /
/ ————————————————– /
DEMO_ROUTINE: PROC;

/ STATIC属性:値を保持し続ける(初期値は0) /
DCL CNT_STATIC FIXED BIN(31) STATIC INIT(0);

/ AUTOMATIC属性:毎回新しく生まれ、終わると消える /
/ (省略してもAUTOMATICになります) /
DCL CNT_AUTO FIXED BIN(31) AUTOMATIC INIT(0);

/ 値をインクリメントして表示する /
CNT_STATIC = CNT_STATIC + 1;
CNT_AUTO = CNT_AUTO + 1;

PUT SKIP EDIT (‘STATIC 変数の値 = ‘, CNT_STATIC) (A, F(4));
PUT SKIP EDIT (‘AUTO 変数の値 = ‘, CNT_AUTO) (A, F(4));

END DEMO_ROUTINE;

このプログラムを実行すると、どうなると思いますか?

画面には次のように出力されます。

— 1回目のルーチン呼び出し —
STATIC 変数の値 = 1
AUTO 変数の値 = 1
— 2回目のルーチン呼び出し —
STATIC 変数の値 = 2
AUTO 変数の値 = 1

おや? `CNT_AUTO` は2回呼び出しても `1` のままですが、`CNT_STATIC` は `2` に増えていますよね。

これがまさにメモリ配置の違いが生み出すマジックです。

  • `CNT_STATIC` はプログラム全体で一つのメモリアドレスを共有し、前回の記憶を保持していました。
  • `CNT_AUTO` は `DEMO_ROUTINE` が呼ばれるたびに新しくメモリが作られ、終わると消滅するため、何回呼んでも「生まれたての1」からスタートしたのです。

4. 再帰呼び出し(RECURSIVE)時の AUTOMATIC の挙動

さて、ここからがPL/Iアーキテクチャの真骨頂です。PL/Iではプロシージャを自分自身で呼び出す「再帰呼び出し(RECURSIVE)」を行うことができます。

もし、再帰呼び出しの中で `AUTOMatic` 変数を使ったらどうなるでしょうか?

1
FACT: PROC(N) RECURSIVE;
DCL N FIXED BIN(31);
DCL SUB_VAL FIXED BIN(31) AUTOMATIC; / 自動変数 /

IF N <= 1 THEN RETURN(1); ELSE DO; SUB_VAL = FACT(N - 1); RETURN(N SUB_VAL); END; END FACT; 階乗(ファクトリアル)を計算するこのコード、`FACT(3)` を呼び出したとします。 再帰呼び出しが行われるたびに、スタック領域の上には新しい `N` や `SUB_VAL` のスペースが「積み木」のように次々と新しく積み上げられていきます。 1回目の呼び出し (`N=3`) の `SUB_VAL` と、2回目の呼び出し (`N=2`) の `SUB_VAL` は、名前は同じでもメモリ上の場所は完全に別モノです。だからこそ、入れ子になって何重に自分自身を呼び出しても、変数の値が混ざってバグるようなことが起きません。

もしここで `SUB_VAL` を `STATIC` にしてしまったら……想像するだけで恐ろしいですね。すべての階層の呼び出しで同じ変数を書き換えてしまうため、計算結果はめちゃくちゃになってしまいます。再帰処理を行うプログラムでは、ローカル変数は必ず `AUTOMATIC`(またはデフォルト)でなければならない理由がここにあります。

5. まとめ:現場での設計・移行時の心構え

レガシーシステムのマイグレーションや保守を行っていると、「なぜこの変数はわざわざ `STATIC` にしてあるんだろう?」「あ、ここは再帰だから `AUTO` じゃなきゃダメだな」といった場面に必ず遭遇します。

  • 状態を保持させたい(カウンターやスイッチなど) ⇒ `STATIC`
  • サブルーチンの中で一時的に使って捨てたい / 再帰で使いたい ⇒ `AUTOMATIC`(省略可)

PL/Iのメモリ管理は、一見すると古めかしく感じるかもしれませんが、突き詰めると非常に合理的で、マシンのリソースをシビアに管理していた先人たちの知恵が詰まった美しい仕組みです。

「一つずつ紐解けば、怖くない」。
今日のこの知識を頭の片隅に置いておけば、明日からのメインフレームのコードリーディングが、少しだけ楽しく、自信を持ってできるようになるはずです。それでは、次回のレガシー探訪もお楽しみに!

タイトルとURLをコピーしました