1. 導入: なぜCONTROLLED変数のFREEが重要なのか?
メインフレーム環境でPL/Iを使用されている皆さんは、記憶域クラスの多様性に日々向き合っていることと思います。その中でも「CONTROLLED」記憶域クラスは、プログラマが明示的に記憶域の割り当てと解放を制御できる強力な機能です。しかし、この「明示的な制御」という特性が、時に予期せぬ落とし穴となります。
特に、プロシージャを抜けても自動では解放されないという特性を持つため、ALLOCATEした記憶域をFREEし忘れると、システム内で古い世代のデータが溜まり続け、やがて深刻なメモリリークを引き起こします。 長期間稼働するオンラインシステムでは、これが原因でシステムのスローダウンや異常終了を招く可能性があり、非常に重要な注意点となります。
2. 基礎知識: CONTROLLED記憶域クラスとその特性
CONTROLLED記憶域クラスは、PL/Iにおいてプログラマが記憶域の確保(ALLOCATE)と解放(FREE)を動的に制御するためのものです。
- ALLOCATEとFREEの明示的制御: CONTROLLED変数は、ALLOCATE文が実行された時に記憶域が確保され、FREE文が実行された時に解放されます。AUTOMATIC記憶域がプロシージャの呼び出しと終了に連動して自動的に確保・解放されるのとは対照的です。
- プロシージャからの独立性: CONTROLLED変数は、それが宣言されたプロシージャを抜けても、ALLOCATEされた記憶域は自動的には解放されません。これは、異なるプロシージャ間や、同じプロシージャが複数回呼び出される際に、その変数の「世代」を管理するために意図された設計です。
- 世代管理: 同じCONTROLLED変数を複数回ALLOCATEすると、新しい世代の記憶域が確保され、以前の世代はスタックのように保持されます。これにより、プログラムは複数の時点の状態を保持したり、履歴データを扱ったりすることができます。
この世代管理の機能は強力ですが、不要になった世代を明示的にFREEしない限り、記憶域は解放されずに残り続けます。これがメモリリークの根本原因となります。
3. 実装/解決策: 明示的なFREEの徹底
CONTROLLED変数を安全に利用するための基本は、ALLOCATEとFREEを常にペアで行うことです。
1. ALLOCATEで記憶域を確保:
DCL DATA_CTL CONTROLLED CHAR(100);
...
ALLOCATE DATA_CTL; / ここで記憶域が確保される /
ALLOCATE文が実行されるたびに、新しい世代のDATA_CTLが作成され、使用可能になります。
2. FREEで記憶域を解放:
FREE DATA_CTL; / 不要になったら、明示的に解放する /
FREE文が実行されると、現在の世代のDATA_CTLが解放され、一つ前の世代が存在すればそれが現在の世代として復元されます。すべての世代が解放されるまでFREEを繰り返すことも可能です。
最も重要なのは、プロシージャの終了処理や、特定の処理ブロックでCONTROLLED変数のデータが不要になった時点で、必ずFREE文を実行することです。特に、オンラインシステムのように同じプロシージャが繰り返し呼び出される環境では、FREE忘れが致命的な問題に発展します。
4. サンプルプログラム
以下に、CONTROLLED変数のALLOCATEとFREEの基本的な使い方を示すPL/Iのサンプルプログラムを示します。
SAMPLE_CTL_PROGRAM: PROC OPTIONS(MAIN);
DCL 1 MY_DATA_STRUCTURE CONTROLLED,
2 FIELD_A CHAR(20),
2 FIELD_B FIXED BINARY(31);
DCL I FIXED BINARY(31);
PUT SKIP LIST('--- CONTROLLED変数使用の開始 ---');
/ 最初の世代をALLOCATE /
ALLOCATE MY_DATA_STRUCTURE;
MY_DATA_STRUCTURE.FIELD_A = 'First Generation';
MY_DATA_STRUCTURE.FIELD_B = 100;
PUT SKIP LIST('1st Gen: ', MY_DATA_STRUCTURE.FIELD_A, MY_DATA_STRUCTURE.FIELD_B);
/ 2番目の世代をALLOCATE /
ALLOCATE MY_DATA_STRUCTURE;
MY_DATA_STRUCTURE.FIELD_A = 'Second Generation';
MY_DATA_STRUCTURE.FIELD_B = 200;
PUT SKIP LIST('2nd Gen: ', MY_DATA_STRUCTURE.FIELD_A, MY_DATA_STRUCTURE.FIELD_B);
/ 現在の世代(2番目)をFREE /
FREE MY_DATA_STRUCTURE;
/ FREE後、1番目の世代が現在の世代となる /
PUT SKIP LIST('After 1st FREE (current is 1st Gen): ', MY_DATA_STRUCTURE.FIELD_A, MY_DATA_STRUCTURE.FIELD_B);
/ 1番目の世代もFREE /
FREE MY_DATA_STRUCTURE;
/ すべての世代が解放された /
PUT SKIP LIST('After 2nd FREE: All generations freed.');
/
- 以下は意図的にFREEを忘れた場合のシミュレーション
- このようなコードがループ内で実行されるとメモリリークを引き起こす
/
DO I = 1 TO 3;
CALL LEAKY_SUBROUTINE;
END;
PUT SKIP LIST('--- CONTROLLED変数使用の終了 ---');
LEAKY_SUBROUTINE: PROC;
DCL LEAKY_VAR CONTROLLED CHAR(50);
ALLOCATE LEAKY_VAR; / 記憶域を確保 /
LEAKY_VAR = 'Data for call ' || TRIM(I);
PUT SKIP LIST('LEAKY_SUBROUTINE called. Allocated: ', LEAKY_VAR);
/ ここで FREE LEAKY_VAR; を忘れている! /
/ プロシージャ終了時に自動解放されないため、世代が蓄積される /
END LEAKY_SUBROUTINE;
END SAMPLE_CTL_PROGRAM;
上記の例では、`LEAKY_SUBROUTINE`内で`FREE LEAKY_VAR;`を意図的にコメントアウトしています。このサブルーチンが何度も呼び出されると、`LEAKY_VAR`の記憶域が解放されずに蓄積され、メモリリークが発生します。
5. 応用・注意点
CONTROLLED変数の利用にあたっては、以下の点に特に注意してください。
- メモリリークの兆候: システムのスローダウン、異常終了(S878など記憶域不足のエラー)、特定のトランザクションの応答時間悪化などが挙げられます。これらの兆候が見られた場合、CONTROLLED変数のFREE忘れを疑うべきです。
- デバッグの難しさ: 記憶域リークは、すぐには表面化せず、長時間稼働した後に問題となることが多いため、原因特定が難しい場合があります。開発段階での入念なテストと、デバッグツールによる記憶域使用状況の監視が重要です。
- 移行時の見極め: 現代的なプログラミング言語(Javaの`ArrayList`やC++の`std::vector`など)における動的配列の概念と、PL/IのCONTROLLED記憶域クラスは似て非なるものです。移行を検討する際には、そのCONTROLLED変数が本当に「世代スタック」として複数の世代を保持する必要があるのか、それとも単に「動的なサイズの配列」が欲しかっただけなのかを見極める必要があります。後者であれば、ポインタとBASED変数、あるいは現代的なコレクションクラスへの置き換えを検討することで、より安全で保守しやすいコードに改善できます。
- オンラインシステムでの影響: 繰り返し実行されるトランザクション処理でFREEを忘れると、短時間のうちに大量のメモリが消費され、システム全体の可用性に深刻な影響を及ぼします。オンラインプログラムでは、必ず出口処理でALLOCATEした記憶域をすべてFREEするロジックを組み込む習慣をつけましょう。
CONTROLLED記憶域クラスは強力な機能ですが、その特性を十分に理解し、明示的なFREEを徹底することで、安定したメインフレームシステムを構築・運用することができます。

コメント