【実務・中級編】CONTROLLED属性によるスタック管理とALLOCATE/FREEのスタック挙動 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近入った若手が「PL/Iって変数名の予約語がないから、何でもかんべんして名前をつけられて怖いッスね」なんて笑いながら話していたんだが……甘い、甘すぎる。

確かにPL/Iには、COBOLのように「何百語もの厳格な予約語リスト」があって、それをうっかり変数名に使ったらコンパイルエラーになる、という世界とは違う。コンテキスト(文脈)で判断してくれる懐の深い言語だ。だがな、その自由度の高さゆえに、一歩使い方を誤ると、地獄のようなデバッグ作業が待っているんだよ。特に、今回取り上げる `CONTROLLED`属性 と、その動的メモリ管理の仕組みを理解していないとな。

メインフレームの基幹バッチで、何世代にもわたる再帰処理や、可変長ツリー構造のデータを扱うとき、`CONTROLLED`変数と `ALLOCATE` / `FREE` のスタック構造を知らずしてコードは書けない。今日は、その実務の現場で絶対に知っておくべき「世代管理の真実」を叩き込んでやる。心して聞けよ。

1. CONTROLLED属性とスタック挙動の基本思想

まず、PL/Iにおける `CONTROLLED`(以下、CTL変数)の基本を叩き込んでおく。
通常の `AUTOMATIC` 変数がプロシージャの開始とともに領域を確保し、終了とともに消え去る(いわゆる一般的なローカル変数)のに対し、CTL変数は プログラマが意図したタイミングで明示的に領域を生成・消滅させることができる

ここがポイントだ。ただの動的割り当てなら `POINTER` を使った `BASED` 変数もある。しかし、`CTL` 変数が圧倒的に優れている(そして恐ろしい)のは、同一名の変数に対して、LIFO(Last-In, First-Out:後入先出)のスタック構造(世代管理)を自動的に構築してくれる点にある。

何が起きているのか?(ALLOCATEとFREEの裏側)

  • `ALLOCATE`文を実行するたびに、OSのワーキングストレージ(あるいはヒープ)上に新しい世代(Generation)のメモリ領域が確保され、既存の同名変数はその裏に「隠される(プッシュされる)」。
  • `FREE`文を実行すると、現在表に出ている最新の世代が解放され、その直前に隠されていた一つ前の世代が再び表舞台に復活する(ポップされる)。

この挙動を理解していないと、再帰呼び出しの中で `ALLOCATE` を叩きまくった挙句、`FREE` の数を見失ってメモリリークを起こしたり、意図しない古い世代のデータを参照してバグの温床を作ったりすることになる。

2. 実践コード:再帰呼び出しと世代スタックの制御

百聞は一見にしかずだ。実際のメインフレームのバッチプログラムを想定して、再帰呼び出しの中で `CTL` 変数がどのように世代管理されるかを示すサンプルコードを書いた。

このコードは、ある組織コード(部門マスタ)を再帰的に辿り、下位組織のデータを集計するバッチ処理をイメージしている。大文字ベース、インデント完備、実務仕様のコメント付きだ。

1
—————————————————————-

  • 組織階層再帰集計プログラム
  • CONTROLLED属性による世代スタックの動作検証用

—————————————————————-
REGCALC: PROC OPTIONS(MAIN);

DCL 1 ORG_DATA CONTROLLED,
2 ORG_ID CHAR(4),
2 BUDGET FIXED DEC(11,2),
2 LEVEL FIXED BIN(15);

DCL TOTAL_BUDGET FIXED DEC(13,2) INIT(0);
DCL MSG_BUFF CHAR(80);

/ 初期ルート組織の処理(レベル1) /
CALL PROCESS_ORG(‘0001’, 100000.00, 1);

PUT SKIP LIST(‘=== 全処理完了 最終集計額 ===’);
PUT SKIP EDIT (TOTAL_BUDGET) (F(13,2));

RETURN;

—————————————————————-

  • 組織処理サブルーチン(再帰呼び出し)

—————————————————————-
PROCESS_ORG: PROC(P_ID, P_BUDGET, P_LEVEL);

DCL P_ID CHAR(4) PARM;
DCL P_BUDGET FIXED DEC(11,2) PARM;
DCL P_LEVEL FIXED BIN(15) PARM;

DCL I FIXED BIN(15);

/ 【重要】ここでCONTROLLED変数を新たにALLOCATEする。 /
/ これにより、呼出し元のORG_DATAはスタックの深層に退避する。 /
ALLOCATE ORG_DATA;

ORG_ID = P_ID;
BUDGET = P_BUDGET;
LEVEL = P_LEVEL;

/ 世代管理の確認用ログ出力(BUILTIN関数 ALLOCATION を使用) /
MSG_BUFF = ‘ALLOCATED: LEVEL=’ || TRIM(CHAR(LEVEL))
|| ‘ 世代数(STACK DEPTH)=’ || TRIM(CHAR(ALLOCATION(ORG_DATA)));
PUT SKIP LIST(MSG_BUFF);

/ 模擬的な下位組織の存在チェックと再帰呼び出し /
/ レベルが2未満であれば、下位組織があるとみなして再帰する /
IF LEVEL < 2 THEN DO; DO I = 1 TO 2; / 再帰呼び出し時、同名のORG_DATAが新世代としてスタックされる / CALL PROCESS_ORG('000' || CHAR(I+1), BUDGET 0.5, LEVEL + 1); END; END; ELSE DO; / 末端組織に到達した場合の集計処理 / TOTAL_BUDGET = TOTAL_BUDGET + BUDGET; END; / 【重要】処理終了に伴い、この世代の領域をFREEする。 / / これにより、直前の世代(親組織のデータ)が自動的に復元される。 / FREE ORG_DATA; END PROCESS_ORG; END REGCALC; ---

3. コードの解説と現場のエンジニアが陥る罠

上記のコードをコンパイルして走らせると、標準出力には次のようなログが流れるはずだ。

ALLOCATED: LEVEL=1 世代数(STACK DEPTH)=1
ALLOCATED: LEVEL=2 世代数(STACK DEPTH)=2
ALLOCATED: LEVEL=2 世代数(STACK DEPTH)=2
=== 全処理完了 最終集計額 ===
100000.00

ここで注目してほしいのが、`ALLOCATION(ORG_DATA)` という組み込み関数だ。これは現在メモリ上に存在している当該 `CTL` 変数の「世代数(スタックの深さ)」を返す。実務のデバッグにおいて、「今、俺は何重のスタックの中にいるのか?」を把握するために非常に強力な武器となる。

現場で頻発するトラブル:アロケーションのミスマッチ

レガシーシステムの改修で最も恐ろしいのは、元の設計者が書いた `ALLOCATE` と `FREE` のペアが、複雑な条件分岐(`IF` や `ON` ユニットの割り込み)によって崩れてしまうことだ。

1. `ALLOCATE` しすぎによるストレージ不足(S80AなどのABEND)
ループ内で `ALLOCATE` を実行したものの、エラー系(`ERROR` や `NAMEERR` などのONユニット)で `FREE` を飛ばしてリターンしてしまった場合、メモリリークを起こし、やがてメインフレームの領域制限を食い潰してアベンドする。
2. `FREE` のしすぎによる未初期化参照
存在しない世代を無理に `FREE` しようとすると、これもまた致命的な例外を引き起こす。

4. ONユニットとの組み合わせにおける注意点

PL/Iの真骨頂といえば `ON` ユニット(例外処理機構)だが、`CONTROLLED` 変数を絡めるときには注意が必要だ。
例えば、ファイル入出力エラー(`ENDFILE` や `ERROR` 条件)が発生して `ON` ユニットに飛ばされた際、そのスコープ内で例外処理として別ルーチンにジャンプ(`GOTO`)したりすると、スタックの整合性が完全に崩壊する。

基本原則として、「`ON` ユニット内で `ALLOCATE` したCTL変数は、その異常系から復帰する、あるいは適切にクリーンアップするロジックを必ず担保する」こと。中途半端な `GOTO` でプロシージャを抜け出すと、OSに返すべき領域が宙ぶらりんになり、次のバッチ実行に悪影響を及ぼす亡霊のようなメモリ領域が残ることになる。

まとめ:先輩からのアドバイス

PL/Iの `CONTROLLED` 属性は、適切に使えば非常にエレガントに再帰処理やツリー構造をメモリ上に構築できる素晴らしい機能だ。しかし、「予約語がない自由な文法」の裏返しとして、コンパイラはポインタ操作や世代管理のミスをすべて人間(プログラマ)の責任として処理する。

もし君が今後、古いバッチプログラムの改修で「なんかこの変数、値が勝手に書き換わってるぞ?」と疑ったら、まずは慌ててダンプを読む前に、その変数が `CONTROLLED` 属性を持っていないか、そして `ALLOCATE` と `FREE` のペアが完璧に対になっているかを疑え。

現場の信頼を勝ち取るシステムエンジニアとは、言語の「便利さ」だけでなく、その「裏側の挙動(スタックとメモリの呼吸)」まで完璧にイメージできるやつのことだ。精進しろよ。

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