PL/Iの「動的メモリ」という深淵:ALLOCATEとDangling Pointerの悪夢を断ち切る
メインフレームの現場で長年バッチ改修を続けていると、若手から「なぜこのポインタが指す先が化けているのか?」という泣きそうな質問を受けることがよくある。PL/Iは非常に強力で自由度が高い言語だが、その自由さゆえに、メモリ管理の作法を誤ると、システム全体を巻き込む致命的な障害(アベンド)を引き起こす。
今日は、PL/Iにおける動的メモリ管理の要である`ALLOCATE`と、避けては通れない「ぶら下がりポインタ(Dangling Pointer)」の罠について、実務の視点から紐解いていこう。
—
1. 予約語なき自由と、ALLOCATEによる「領域の呪縛」
PL/Iの最大の特徴の一つは、「予約語」という概念が希薄であることだ。極端な話、`IF`や`THEN`さえも変数名として使えてしまう(もちろん推奨はしない)。しかし、この柔軟性は、コンパイラが「どこまでが識別子で、どこからが命令か」を厳密に解析しているからこそ成り立っている。
この自由な設計思想の延長にあるのが、`BASED`変数と`ALLOCATE`文による動的メモリ管理だ。
/i
/ BASED変数の定義例 /
DCL 1 WORK_AREA BASED(P_WORK),
2 REC_ID CHAR(8),
2 DATA_VAL FIXED BIN(31);
DCL P_WORK POINTER;
/ ヒープ領域からメモリを確保 /
ALLOCATE WORK_AREA;
ここで重要なのは、`ALLOCATE`が実行されるたびに、ヒープ領域から要求されたサイズのメモリが切り出され、その先頭アドレスがポインタ変数(`P_WORK`)に格納されるという点だ。VSAMの大量レコードをバッファリングする際や、複雑な連結リスト構造を動的に生成する際には欠かせない技術だが、ここで最大の落とし穴が待っている。
—
2. FREE文は「消去」ではなく「解放」である
現場で最も多いミスは、`FREE`文を実行した後に、そのポインタが「まだ有効である」と勘違いすることだ。
/i
/ メモリの解放 /
FREE WORK_AREA;
/ 悪い例:解放後もポインタを参照してしまう /
IF P_WORK -> REC_ID = ‘END’ THEN …
`FREE`を実行しても、ポインタ変数 `P_WORK` の中身(アドレス値)は自動的にはリセットされない。これが、いわゆる「ぶら下がりポインタ」の正体だ。OSのメモリ管理機構は、すでにその領域を別の用途に割り当てているかもしれない。そこで旧ポインタからデータを読み書きしようとすれば、無関係なデータの破壊や、最悪の場合は`S0C4`(保護例外)によるバッチのアベンドを招く。
実践的な防衛策:ポインタの「明示的NULL化」
私は常々、チームメンバーにこう指導している。「`FREE`を実行したら、直後に必ずポインタを`NULL()`で初期化せよ」と。
/i
/ 正しい作法:解放と同時にポインタを無効化する /
FREE WORK_AREA;
P_WORK = NULL(); / これが鉄則 /
/ 安全な参照チェック /
IF P_WORK ^= NULL() THEN DO;
/ 確実に有効な領域のみアクセスする /
END;
—
3. VSAMアクセスとONユニットの組み合わせによる安全網
大規模バッチでは、入出力エラーを制御するために`ON`ユニットを多用するだろう。しかし、メモリ管理と例外処理が絡むと、一気に難易度が上がる。
例えば、`ALLOCATE`したメモリを使い切る前に`READ`エラーが発生し、`ON ENDFILE`や`ON ERROR`で制御が移った場合、メモリリークが発生しやすくなる。
/i
/ 例外処理のフレームワーク /
ON ERROR BEGIN;
PUT SKIP LIST(‘予期せぬエラー発生: メモリ解放を検討’);
/ 必要なクリーンアップ処理 /
IF P_WORK ^= NULL() THEN FREE WORK_AREA;
SIGNAL ERROR; / 必要に応じて再送出 /
END;
/ VSAM読み込みとメモリ確保のループ /
DO WHILE(MORE_RECORDS);
READ FILE(VSAM_IN) INTO(WORK_AREA); / ここで動的に確保する手もある /
/ … 処理 … /
END;
—
ベテランからのアドバイス:デバッグのコツ
もし本番稼働中に「原因不明のデータの化け」が発生したら、真っ先に疑うべきは「解放済みの領域への書き込み」か「ポインタの不正な初期化」だ。
- NULL()の活用: 常にポインタは `NULL()` で初期化する習慣をつけよ。未初期化ポインタがどこを指しているかを追うのは、泥沼のデバッグ作業になる。
- ADDRビルトイン関数: 意図したアドレスを指しているか、`DISPLAY`文で `ADDR()` を出力してログを確認する癖をつけること。
- ストレージ管理: 大規模なメモリ確保が必要な場合は、個別の`ALLOCATE`ではなく、`AREA`属性を用いたストレージ・プール管理を検討せよ。メモリ断片化(フラグメンテーション)を防ぐための、プロのテクニックだ。
PL/Iは古い言語と言われることもあるが、この「ハードウェアに近い制御」ができるからこそ、現代のクラウド環境でもメインフレームは生き残っている。メモリを制する者は、システムを制する。ぜひ、この「解放後のNULL化」を今日からコード規約に加えてみてほしい。それだけで、君のコードの信頼性は劇的に向上するはずだ。
