はじめに:ADDR関数と実行時評価の重要性
メインフレームの世界では、プログラムがメモリ上のどこに配置されているかを正確に把握することが、効率的で安全なプログラム開発の鍵となります。特に、`ADDR`組み込み関数は、変数や構造体のメモリアドレスを取得するために使用されますが、その評価タイミングが「実行時」であるという特性を理解することは非常に重要です。この実行時評価が、プログラムの予期せぬ動作や、最悪の場合、メモリリークや不正アクセスといった深刻な問題を引き起こす可能性があるからです。
このTipsでは、`ADDR`関数がどのように変数のアドレスを取得し、特にポインタ変数やロケータ変数との組み合わせで、どのような挙動を示すのかを解説します。さらに、現代のプログラミング言語との比較を通じて、PL/Iにおけるポインタの「スコープ外参照」という特性がもたらすリスクと、その対策についても掘り下げていきます。
基礎知識:ADDR関数、ポインタ、ロケータとは?
- ADDR組み込み関数:
`ADDR(変数名)` の形式で記述され、指定した変数や構造体のメモリアドレスを返します。重要なのは、このアドレスは「コンパイル時」ではなく、「その文が実行された瞬間」に決定されるという点です。つまり、プログラムの実行中に変数の位置が変われば、`ADDR`関数が返すアドレスも変わる可能性があります。
- ポインタ変数:
ポインタ変数は、他の変数や構造体のメモリアドレスを格納するための変数です。`P = ADDR(VAR);` のように、`ADDR`関数で取得したアドレスをポインタ変数に代入して使用します。これにより、間接的に変数にアクセスしたり、動的にメモリを管理したりすることが可能になります。
- ロケータ変数 (PL/I特有):
PL/Iでは、ポインタ変数に加えて、構造体や配列の特定の位置を指し示す「ロケータ変数」という概念があります。これは、ポインタ変数と似ていますが、より構造化されたデータへのアクセスに特化しています。
- 実行時評価 vs コンパイル時評価:
多くの現代的な言語では、変数へのアクセスはコンパイル時にその場所がほぼ固定されます。しかし、`ADDR`関数のように実行時にアドレスを決定するものを「実行時評価」と呼びます。この違いが、特にPL/Iのポインタ挙動において重要な意味を持ちます。
実装/解決策:「再入可能」なプロシージャとポインタの生存期間
PL/Iのポインタ、特に「AUTOMATIC」属性を持つ変数のアドレスを`ADDR`関数で取得し、それをポインタ変数に格納した場合、注意が必要です。
`AUTOMATIC`変数は、その変数が宣言されたプロシージャ(サブルーチンや関数)が呼び出されるたびに、新たにメモリ領域が確保されます。プロシージャを抜けると、その領域は解放されるのが一般的です。
しかし、PL/Iのポインタは、プロシージャが終了しても、そのポインタが指していたアドレス情報(番地)が残ってしまうことがあります。これを「スコープ外ポインタ参照」と呼びます。
例えば、以下のようなシナリオを考えてみましょう。
1. プロシージャAが呼び出され、`AUTOMATIC`変数Xのアドレスを取得し、ポインタPに格納する (`P = ADDR(X);`)。
2. プロシージャAが終了する。この時、変数Xのメモリ領域は解放されるはずだが、ポインタPは古いアドレスを保持したまま。
3. 再度プロシージャAが呼び出され、再び`AUTOMATIC`変数Xが確保される。しかし、これは以前とは異なるメモリ領域に確保される可能性がある。
4. ポインタPを使って変数Xにアクセスしようとすると、期待しないメモリ領域にアクセスしてしまう可能性がある。
この挙動は、Javaなどの現代言語で、メソッドを抜けるとスタック変数の参照が無効になるのと対照的です。Javaでは、メソッド終了時にローカル変数の参照は自動的に無効になりますが、PL/Iでは、ポインタが古いアドレスを保持し続けるため、意図しないメモリ領域を参照してしまうリスクがあります。
サンプルプログラム:ADDR関数の挙動を確認する
ここでは、`ADDR`関数とポインタ変数の基本的な使い方、そして再入可能なプロシージャにおけるアドレスの変動を確認する簡単な例を示します。
/—————————————————————-
- ADDR関数の実行時評価とポインタの挙動を確認するサンプルプログラム
—————————————————————-/
PROGRAM: PROC OPTIONS(MAIN);
DCL MY_VAR CHAR(10) VARYING AUTO; / AUTO属性の変数 /
DCL P POINTER; / ポインタ変数 /
DCL PROC_ADDR_1 ENTRY RETURNS(POINTER); / プロシージャの戻り値(ポインタ) /
/ プロシージャの宣言 /
PROC PROC_ADDR_1() RETURNS(POINTER);
DCL LOCAL_VAR CHAR(10) VARYING AUTO; / プロシージャ内のAUTO変数 /
P = ADDR(LOCAL_VAR); / プロシージャ実行時のLOCAL_VARのアドレスを取得 /
PUT SKIP LIST(‘— PROC_ADDR_1内 —‘);
PUT SKIP LIST(‘LOCAL_VARのアドレス: ‘, ADDR(LOCAL_VAR));
PUT SKIP LIST(‘ポインタPに格納されたアドレス: ‘, P);
RETURN(P); / 取得したアドレスを返す /
END PROC_ADDR_1;
/ メイン処理 /
MY_VAR = ‘Hello PL/I’;
/ 1回目のプロシージャ呼び出し /
PUT SKIP LIST(‘— 1回目の呼び出し —‘);
P = PROC_ADDR_1(); / プロシージャを呼び出し、戻り値のアドレスをPに格納 /
PUT SKIP LIST(‘プロシージャ返却後、ポインタP: ‘, P);
/ 注意:ここではPはプロシージャ内のLOCAL_VARのアドレスを指しているが、
LOCAL_VARは既に解放されている可能性がある。
このPを使ってLOCAL_VARにアクセスすると危険。 /
/ 2回目のプロシージャ呼び出し /
PUT SKIP LIST(‘— 2回目の呼び出し —‘);
P = PROC_ADDR_1(); / 再度プロシージャを呼び出し、新しいアドレスをPに格納 /
PUT SKIP LIST(‘プロシージャ返却後、ポインタP: ‘, P);
/ 2回目では、LOCAL_VARは異なるメモリ領域に確保される可能性があり、
Pは新しいアドレスを指す。 /
/ AUTO変数のアドレスを直接取得してみる /
PUT SKIP LIST(‘— メイン処理でのADDR(MY_VAR) —‘);
PUT SKIP LIST(‘MY_VARのアドレス: ‘, ADDR(MY_VAR));
/ MY_VARはメイン処理のAUTO変数なので、このブロック内では
アドレスは一定であることが期待できる。 /
END PROGRAM;
このサンプルプログラムでは、`PROC_ADDR_1`というプロシージャを定義し、その中で`AUTOMATIC`属性の`LOCAL_VAR`のアドレスを取得してポインタ`P`に格納しています。
プログラムを実行すると、`PROC_ADDR_1`が呼び出されるたびに`LOCAL_VAR`に確保されるアドレスが変化し、ポインタ`P`もそれに追随して更新される様子が確認できます。
応用・注意点:スコープ外ポインタ参照のリスクと対策
PL/Iのポインタが持つ「スコープ外ポインタ参照」の特性は、非常に強力である反面、大きなリスクも伴います。
- メモリリーク:
解放されるべきメモリ領域を、不要になったポインタが指し続けていると、そのメモリ領域はプログラムから利用できなくなり、事実上のメモリリークとなります。
- 不正アクセス(セグメンテーション違反など):
解放済みのメモリ領域や、全く関係のないメモリ領域をポインタが指している状態でその領域にアクセスしようとすると、プログラムが異常終了したり、予期しないデータが書き換えられたりする可能性があります。
現場で役立つ対策:
1. ポインタの初期化とNULL化:
ポインタ変数は、使用しないときは`NULL`ポインタ(アドレスを持たない状態)に初期化することが推奨されます。
DCL P POINTER INIT(NULL);
また、ポインタが指すメモリ領域が不要になったら、そのポインタを`NULL`に設定する習慣をつけましょう。
2. ポインタの生存期間(Scope)の理解:
ポインタがどの変数を指しているのか、そしてその変数がいつまで有効なのかを常に意識することが重要です。特に`AUTOMATIC`属性の変数とポインタを組み合わせる場合は、プロシージャの呼び出し履歴を追跡できるように、慎重に設計する必要があります。
3. 動的メモリ管理の利用:
`ALLOCATE` / `FREE`のような動的メモリ管理機能を使用する場合、メモリの確保と解放のペアを厳密に管理し、解放漏れがないように注意してください。
4. 移行時の注意:
他の現代的な言語からPL/Iへ移行する場合、この「スコープ外ポインタ参照」の挙動は特に注意が必要です。移行前に、既存のコードでポインタがどのように使われているか、メモリリークや不正アクセスのリスクがないかを徹底的に検証することが最優先事項となります。
`ADDR`組み込み関数とポインタ変数の実行時評価の特性を理解し、これらの注意点を遵守することで、PL/Iでの安全かつ効率的なプログラム開発が可能になります。

コメント