こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他のモダンな言語の経験をお持ちの方にとって、IBMメインフレームの世界、そしてそこで動く「PL/I(ピーエルアイ)」という言語は、少し古めかしく、どこか近寄りがたい雰囲気を醸し出しているかもしれません。
特に、データ宣言やメモリ管理の仕様を聞くと、「なんだか難解そう」「間違えたらシステムが落ちそう」と身構えてしまいますよね。でも、安心してください。基本のルールさえ掴んでしまえば、PL/Iは非常に合理的で、プログラマの意図に忠実に動いてくれる頼もしい相棒です。
今回は、そんなPL/Iの数ある機能の中でも、基幹システムのバッチ処理などで避けて通れない「ALLOCATE文による動的ストレージ獲得とヒープ管理(ストレージリークの恐怖と対策)」について、優しく、そして深く紐解いていきたいと思います。
—
1. 他言語とはちょっと違う?PL/Iのメモリ管理の心構え
Javaを使っている方なら「ガーベージコレクション(GC)が勝手にメモリを掃除してくれる」、COBOLを使っている方なら「ワーキングストレージセクションに書いたデータ部は最初から最後まで安全に存在している」という感覚が当たり前だと思います。
しかし、PL/Iの世界はちょっと違います。
PL/Iには、「必要なときに、必要な分だけメモリ(ヒープ領域)を自分で直接つかみ取り、使い終わったら責任を持って自分の手で返す」という、C言語の `malloc` や `free` に似たアグレッシブな動的メモリ管理の文化が存在します。
ここで登場するのが、今回の主役である `ALLOCATE`文 と `FREE`文 です。
—
2. そもそも「ストレージリーク」ってなに?
基幹システムの夜間バッチ処理などで、こんな怪現象に遭遇したことはありませんか?
> 「最初は順調に動いていたのに、処理件数が増えるにつれてだんだんCPU使用率が上がり、最後には『S80A』や『S0C4』といった見慣れない異常終了(ABEND)コードを残してバッチが突然死した……」
これは典型的な「ストレージリーク(メモリリーク)」の症状です。
イメージしてみてください。
あなたは大きな倉庫(メインフレームのヒープ領域)の管理人です。お客さん(プログラム)から「段ボール箱を置く場所が欲しい!」と頼まれるたびに、空いているスペースを `ALLOCATE`(割り当て)して貸し出します。
本来なら、使い終わった段ボール箱は片付けられて `FREE`(解放)されるべきですよね。しかし、プログラムの書き方に不備があり、「貸し出したはいいが、返却の手続きを忘れてしまった」あるいは「エラーで処理を抜けたときに解放処理がスキップされてしまった」とします。
これを何百万回、何千万回と繰り返すとどうなるでしょうか?
そう、倉庫の空きスペースが徐々に埋まり、やがて「新しくメモリを貸し出す場所がありません!」とシステムが悲鳴を上げることになります。これがPL/Iにおけるヒープ領域の枯渇です。
—
3. 実践!ALLOCATEとFREEの正しい作法
言葉だけだと少し怖いかもしれないので、実際のPL/Iコードを見てみましょう。
今回は、可変長のデータを動的に扱う典型的な例を書いてみます。
1
/ ========================================================== /
/ 動的ストレージ管理(ALLOCATE / FREE)のサンプルプログラム /
/ ========================================================== /
TEST_ALLOC: PROC OPTIONS(MAIN);
/ 1. ベースとなる構造体の定義(BASED属性を付与するのがポイント) /
DECLARE WORK_RECORD_PTR POINTER; / アドレスを保持するポインタ変数 /
DECLARE 1 WORK_RECORD BASED(WORK_RECORD_PTR),
2 REC_ID CHAR(5), / レコードID /
2 REC_DATA CHAR(100); / データ本体 /
DECLARE I FIXED BIN(31);
/ 2. ループ内で動的にメモリを獲得し、処理を行う例 /
DO I = 1 TO 1000000;
/ 【重要】ALLOCATE文でヒープ領域からメモリを獲得する /
ALLOCATE WORK_RECORD;
/ 獲得した領域に値をセット /
REC_ID = ‘ID’ || PUT_NO(I); / ※PUT_NOはイメージです /
REC_DATA = ‘ここに大切な業務データを格納します’;
/ — ここで何らかのファイル出力や編集処理を行う — /
/ 【超重要】使い終わったら必ずFREEでメモリを返す! /
FREE WORK_RECORD;
END;
PUT SKIP LIST(‘正常に処理が完了しました。’);
END TEST_ALLOC;
ここがポイント:BASED属性とポインタ
PL/Iで動的メモリを扱う際、データ定義に `BASED(ポインタ変数名)` という属性をつけます。
これによって、「このデータ構造体は、いまポインタ変数が指している実メモリの場所を間借りして読み書きしますよ」という宣言になります。
`ALLOCATE WORK_RECORD;` を実行した瞬間、システムはヒープ領域から `WORK_RECORD` のサイズ分のメモリを切り出し、その住所を自動的に `WORK_RECORD_PTR` に格納してくれます。
—
4. 事故を防ぐためのシニア・アーキテクトからの知恵袋
実務の現場では、上記のように「ALLOCATEしたらすぐ下でFREEする」という綺麗なパターンばかりではありません。複雑な条件分岐、途中で発生する例外処理(ON条件など)、サブルーチンへの引き渡しなどが絡み合うと、うっかり `FREE` を書き忘れたり、パスによっては解放処理がバイパスされてしまうことがあります。
ここで、レガシー移行や保守の現場で役立つ実践的な知見をいくつかお伝えします。
① ポインタの「上書き」に要注意!
1
ALLOCATE WORK_RECORD;
/ FREEを忘れたまま、もう一度ALLOCATEしてしまうと… /
ALLOCATE WORK_RECORD;
これをしてしまうと、最初に獲得したメモリを指していたアドレス(ポインタ)が、新しく獲得したメモリのアドレスで上書きされてしまい、最初に確保したメモリの住所が分からなくなって二度と解放できなく(デッドリンク)なります。これは悪質なストレージリークの定番原因です。ALLOCATEする前には、必ず既にポインタが使われていないか(あるいは直前のFREEが確実に行われているか)を確認しましょう。
② エラー時のク traitement(後始末)を考える
バッチプログラムが途中で異常終了(ABEND)や、PL/Iの `SIGNAL` や `GOTO` で処理を大きくジャンプする場合、途中の `FREE` 文が実行されないままプログラムが終了することがあります。
モダンなOSであればプロセス終了時にメモリは一括回収されますが、長期間稼働するCICSなどのオンラインシステムや、巨大なジョブステップ内では、これが致命的なセッションリークにつながります。エラーパスを通った場合でも確実にメモリが解放されるような、クリーンアップルーチンの設計を心がけましょう。
—
おわりに
いかがでしたでしょうか?
PL/Iの `ALLOCATE` による動的ストレージ管理は、一見すると泥臭く、少し緊張感のある作業に思えるかもしれません。しかし、それは裏を返せば「コンピュータのメモリをプログラマの意図通りに極限までコントロールできる」という、メインフレームならではの強力な武器でもあります。
「ポインタとBASED属性」「ALLOCATEしたら必ずセットでFREEする」という基本の鉄則さえ守っていれば、ストレージリークは怖くありません。
もし現場のレガシーコードで「最近、このバッチだけメモリ食いが激しいな…」というソースを見つけたら、ぜひ今回の記事を思い出して、ポインタの動きと `ALLOCATE`/`FREE` の対関係をじっくり追ってみてくださいね。
それでは、快適なメインフレーム・ライフを!
