こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他のプログラミング言語の経験がある方にとって、初めて目にするPL/I(ピーエルアイ)のソースコードは、ちょっと独特で宇宙語のように感じられるかもしれませんよね。「なんだこの書き方は……」と身構えてしまうお気持ち、とてもよく分かります。
でも、安心してください。ベースにある考え方は他の言語と変わりません。一歩ずつ、私と一緒に紐解いていけば、怖がる必要なんて全然ないんです。
今回は、そんなPL/Iの数ある特徴の中から、「INITIAL属性による変数の初期化タイミング」と、コンパイラが裏側で行っている「ちょっとしたお節介(最適化)」について、実務の現場の視点を交えながら分かりやすく解説していきますね。
—
1. 他の言語とはちょっと違う?PL/Iの変数初期化(INITIAL属性)
Javaなら変数を宣言したときに `= 0` や `= “”` と書いたり、COBOLなら `VALUE` 句を書いたりしますよね。PL/Iでもこれに相当する仕組みがあって、それが `INITIAL`属性(または `INIT`) です。
例えば、こんな風に書きます。
1
DCL WK_COUNT FIXED BIN(31) STATIC INIT(0);
DCL WK_NAME CHAR(10) STATIC INIT(‘IBM’);
「おっ、JavaやCOBOLと同じだな」と思いました?
実はここに、メインフレームの歴史とPL/I特有の「メモリ管理の哲学」が隠されているんです。
いつ、誰が初期化しているの?
JavaやCOBOLの感覚だと、「プログラムが動いた瞬間(あるいはメモリが割り当てられた瞬間)に、自動で値がセットされるんでしょ?」と思いますよね。もちろん結果としてはそうなのですが、PL/Iの `INITIAL` 属性には、「いつその初期値が書き込まれるか」という重要なタイミングのルールがあります。
それが決まる最大の要因が、「ストレージクラス(メモリをどう扱うか)」という属性です。
- `STATIC`(静的領域)の場合
プログラムがロードされ、実行が始まる「ほんの一番最初(1回だけ)」に初期値が書き込まれます。2回目以降にそのサブルーチンが呼ばれても、`INIT(0)` が再度実行されて値がリセットされることはありません(前回の保持された値がそのまま残ります)。ここ、C言語の `static` と同じ感覚ですね。
- `AUTOMATIC`(自動領域 / ローカル変数)の場合
ブロック(PROCEDUREやBEGINブロック)に入って、メモリが割り当てられるたびに、「その都度」初期値が設定されます。
—
2. 動的メモリ割り当て(ALLOCATE)とINITIALの恋物語
PL/Iの強力な機能の一つに、必要なときに必要なだけメモリを確保する `ALLOCATE`文(動的変数) があります。Javaの `new` や、Cの `malloc` のようなものです。
この `ALLOCATE` を行うときにも、`INITIAL` 属性は大活躍します。
1
/ 構造体の定義(BASED変数) /
Dcl 1 顧客データ Based(P_顧客),
2 顧客ID Char(5) Init(‘00000’),
2 顧客名 Char(30) Init(‘未設定’);
/ 動的なメモリ確保 /
Allocate 顧客データ;
このコードを実行すると、何が起きるでしょうか?
`ALLOCATE 顧客データ;` とコンピュータに命令したその瞬間に、OSからメモリが切り出され、コンパイラが生成したコードによって自動的に `顧客ID` に `’00000’` が、`顧客名` に `’未設定’` が書き込まれます。
Javaのように「インスタンス生成後にコンストラクタを手動で呼ぶ」なんて手間は不要で、メモリ確保と初期化がアトミック(不可分)に行われるのがPL/Iのスマートなところです。
—
3. コンパイラの「お節介」?最適化による初期化の省略
さて、ここからが今回のメインディッシュであり、現場でトラブルシューティングをする際に知っておくべき重要なお話です。
現代のIBM Enterprise PL/Iコンパイラは、それはもう非常に優秀で頭が良いです。「いかにプログラムを高速に動かすか(CPUサイクルを減らすか)」を常に考えています。
ここで、こんなコードを想像してみてください。
1
SUB_PROC: PROC;
DCL WK_WORK CHAR(100) AUTOMATIC; / INITIALがないローカル変数 /
WK_WORK = ‘HELLO’; / メモリが割り当てられて即座に値を代入 /
PUT SKIP LIST(WK_WORK);
END SUB_PROC;
もし、この `WK_WORK` に `INIT(‘ ‘)`(スペース埋め)をわざわざ書いたとしましょう。
1
DCL WK_WORK CHAR(100) AUTOMATIC INIT(‘ ‘);
一見、「ちゃんと初期化していて安全だな」と思いますよね。しかし、コンパイラはこう考えます。
> 「おいおい、この変数、どうせ次の行で一文字残らず `’HELLO’` で上書きするじゃないか。わざわざ最初にスペースでメモリを埋める(初期化する)処理を挟んだら、CPUの時間と命令キャッシュの無駄遣いだよ!」
結果として、コンパイラ(オプティマイザ)は賢い判断を下し、「INITIALによる初期化処理のコード自体を消し去る(最適化する)」ことがあります。
これが原因で現場で起きる「ヒヤリハット」
他の言語から移行してきたプログラマがやりがちなのが、次のような思い込みです。
「INITIALにこう書いておけば、プログラムのどこからでもデフォルト値が安全に取れるはずだ」
しかし、もしその変数が `AUTOMATIC` であり、かつ最適化のレベル(`OPT(2)` や `OPT(3)` など)が高く設定されている場合、私たちが意図したタイミングでの初期化が省かれたり、未定義のゴミデータ(前回別の処理で使われたメモリの残り香)が残っていたりして、予期せぬバグ(S0C4やデータ化け)を引き起こす原因になることがあります。
特に、レガシーシステムのマイグレーションにおいて、古いコンパイラから最新のEnterprise PL/Iへバージョンアップした途端に、こうした「暗黙の前提に頼った初期化漏れ」が表面化し、テスト工程で冷や汗をかくというのは、現場では「あるある」のドラマだったりします。
—
まとめ:安全なPL/Iプログラミングのために
いかがでしたでしょうか?
PL/Iの `INITIAL` 属性は非常に便利ですが、
1. `STATIC` と `AUTOMATIC` で初期化のタイミング(1回きりか、毎回か)が異なること
2. `ALLOCATE` 時にはメモリ確保と同時に確実に初期化が走ること
3. コンパイラの最適化によって、無駄な初期化コードは消される可能性があること(=初期化を過信せず、自分で明示的に代入する意識が大切)
この3つのポイントを頭の片隅に置いておくだけで、コードを読むときの解像度が劇的に上がります。
「なんだ、基本を押さえれば怖くないじゃないか」と思っていただけたなら嬉しいです。
メインフレームの世界は奥が深いですが、一つひとつの仕様にはちゃんと理屈があります。ぜひ、日々の開発や移行作業を楽しんでくださいね!それではまた、次回の技術解説でお会いしましょう。
