【PL/I学習|実務向け】mainframe ADDR関数: 定数に適用できない理由と賢い回避策

導入: なぜADDR関数で定数に悩むのか?

メインフレーム開発者の皆さん、日々の業務お疲れ様です。今回は、PL/Iでよく利用されるADDR関数と、その「定数」への適用に関する落とし穴について解説します。ADDR関数は変数のメモリアドレスを取得する便利な関数ですが、リテラル定数(例: ‘ABC’ や 123)に直接適用しようとするとエラーになります。この制限を理解せず、不用意にADDR関数を定数に適用しようとすると、プログラムのコンパイルエラーや、予期せぬ動作を引き起こす可能性があります。本記事では、このADDR関数の制約の理由と、現場で役立つ具体的な回避策、さらには移行時の最適化についても触れていきます。

基礎知識: ADDR関数と「定数」とは?

ADDR関数とは

PL/IにおけるADDR関数は、引数として渡された変数のメモリアドレス(ポインタ)を返します。これは、特定のメモリ位置に直接アクセスしたい場合や、他のプログラムとデータを共有する際に非常に役立ちます。例えば、ある変数が格納されているメモリ上の場所を知ることで、その変数の内容を効率的に操作することが可能になります。

「定数」とは

ここでいう「定数」とは、プログラムの実行中に値が変更されない固定値のことを指します。PL/Iでは、 `’ABC’` のような文字リテラルや `123` のような数値リテラルなどがこれに該当します。これらの定数は、プログラムコードの一部として直接埋め込まれている場合が多く、そのアドレスはコンパイラによって決定されます。

なぜ定数にADDR関数を適用できないのか?

リテラル定数にADDR関数を適用できない主な理由は、その「アドレスの不確定性」と「メモリ保護」にあります。

  • アドレスの不確定性: リテラル定数は、プログラムの実行時に必ずしも特定の固定メモリ領域に配置されるとは限りません。コンパイラは、コードの最適化や他の定数との共有のために、定数の配置場所を動的に決定することがあります。そのため、実行時に「この定数のアドレスはこれです」と断定することが難しいのです。
  • メモリ保護: 定数は、プログラムの誤った操作から保護するために、一般的に読み取り専用のメモリ領域に配置されることがあります。もしADDR関数で定数のアドレスを取得できてしまうと、そのアドレスを通じて定数の値を書き換えてしまうといった、意図しないメモリ破壊を引き起こす危険性があります。

これらの理由から、PL/Iの言語仕様として、ADDR関数は実行時にアドレスが明確に定義され、かつ書き換え可能な「変数」に対してのみ適用が許可されています。

実装/解決策: 変数への代入でADDR関数を有効活用する

リテラル定数にADDR関数を直接適用できない場合、どのようにしてその「アドレス」を扱えば良いのでしょうか?最もシンプルで推奨される方法は、一度変数を介してADDR関数を適用することです。

例えば、 `’ABC’` という文字リテラルのアドレスを取得したい場合、直接 `ADDR(‘ABC’)` とすることはできません。しかし、以下のように一時的な変数に `’ABC’` を代入し、その変数のアドレスを取得することは可能です。

1. 一時変数を用意する: 対象となる定数と同じデータ型と長さを持つ変数を宣言します。
2. 定数を変数に代入する: 宣言した変数に、目的の定数値を代入します。
3. ADDR関数で変数のアドレスを取得する: ADDR関数を、代入後の変数に適用します。

この手順を踏むことで、定数そのものではなく、その定数値を保持する変数のアドレスを取得することができます。これは、現代のプログラミング言語において、定数オブジェクトへの参照を渡す際のアプローチと似ています。

サンプルプログラム: 実際に試してみよう

ここでは、文字リテラルと数値リテラルを例に、ADDR関数を一時変数経由で適用するサンプルコードを示します。

1
/ Sample Program: ADDR function with constants via variables /
MAIN: PROC OPTIONS(MAIN);

DCL C_VAR CHAR(3); / 文字リテラルを格納するための一時変数 /
DCL P_CHAR PTR; / 文字変数用のアドレス(ポインタ) /

DCL N_VAR FIXED BIN(31); / 数値リテラルを格納するための一時変数 /
DCL P_NUM PTR; / 数値変数用のアドレス(ポインタ) /

/ 文字リテラル ‘ABC’ を変数 C_VAR に代入 /
C_VAR = ‘ABC’;
/ 変数 C_VAR のアドレスを P_CHAR に格納 /
P_CHAR = ADDR(C_VAR);

/ 数値リテラル 123 を変数 N_VAR に代入 /
N_VAR = 123;
/ 変数 N_VAR のアドレスを P_NUM に格納 /
P_NUM = ADDR(N_VAR);

/ 結果の表示 /
/ P_CHAR に格納されたアドレスを表示(実機ではアドレス値が表示されます) /
PUT SKIP LIST(‘Address of C_VAR (containing ”ABC”):’, P_CHAR);
/ P_NUM に格納されたアドレスを表示(実機ではアドレス値が表示されます) /
PUT SKIP LIST(‘Address of N_VAR (containing 123):’, P_NUM);

/ アドレス経由で値を変更する例(注意:これは変数の内容を変更します) /
/ P_CHAR を介して ‘XYZ’ を代入 /
/ 注意: この操作は P_CHAR が指す C_VAR の内容を変更します /
/ 実際のポインタ演算と代入は、より複雑な場合があります。 /
/ ここでは概念的な例として示します。 /
/
IF P_CHAR ^= NULL THEN
%LET P_CHAR = ‘XYZ’; <- これはPL/Iの構文ではありません。 / / 上記は構文エラーになるため、直接変数に代入するのが一般的です。 / / PUT SKIP LIST('Attempting to change via pointer...'); IF P_CHAR ^= NULL THEN

  • ここで P_CHAR が指すメモリ位置に ‘XYZ’ を書き込む処理を記述しますが、 /

/ PL/I ではポインタ経由の直接的な文字列代入は一般的ではありません。 /
/ 通常は、ポインタを介して構造体メンバーなどにアクセスします。 /
/ 例として、C_VAR の内容を直接変更します。 /
C_VAR = ‘XYZ’;
PUT SKIP LIST(‘Modified C_VAR to:’, C_VAR);

/ P_NUM を介して 456 を代入 /
/ 同様に、ポインタ経由での直接代入は一般的ではないため、N_VAR を直接変更します。 /
N_VAR = 456;
PUT SKIP LIST(‘Modified N_VAR to:’, N_VAR);

RETURN;
END MAIN;

このサンプルプログラムでは、`’ABC’` という文字リテラルと `123` という数値リテラルを、それぞれ `C_VAR` と `N_VAR` という変数に代入しています。そして、ADDR関数を使ってこれらの変数 `C_VAR` と `N_VAR` のアドレスを取得しています。`PUT SKIP LIST` で表示されるのは、それぞれの変数に割り当てられたメモリアドレスです。

注意点: サンプルコードの後半にあるポインタ経由での値変更部分は、PL/Iにおけるポインタ操作の複雑さを示すためのものです。実際には、ポインタを直接文字列や数値の代入に使うよりも、構造体や配列の要素にアクセスする際などに利用されることが多いです。この例では、概念を理解していただくために、直接変数の内容を変更する形に修正しています。

応用・注意点: 現場での最適化と移行時の考慮事項

冗長な一時変数の整理

PL/Iの古いコードや、ADDR関数を定数に適用できない制約を回避するために、意図せず大量の一時変数が定義されているケースが見受けられます。特に、定数値を保持するためだけに宣言された変数などがこれに該当します。

現代のプログラミング言語の感覚でコードを見ると、これらの変数は「冗長」に映ることがあります。しかし、メインフレームの移行プロジェクトなどでは、これらのコードをいきなり削除・変更すると、予期せぬ影響が出る可能性があります。

移行時には、これらの「冗長な一時変数」を慎重にレビューし、定数値を直接インライン化(コード中に直接埋め込む)したり、より効率的なデータ構造に置き換えたりすることで、コードの可読性や保守性を向上させることができます。ただし、その変更がプログラムのロジックに影響を与えないかを十分にテストすることが不可欠です。

ポインタとロケータの理解

ADDR関数が返すのは「ロケータ」と呼ばれるもので、これはメモリ上の特定の位置を指し示すものです。PL/Iでは、このロケータ変数(ポインタ変数)を介して、間接的にデータにアクセスすることが可能です。

ADDR関数で取得したロケータ変数は、以下のような場面で活用されます。

  • 動的なメモリ割り当て: 実行時に必要なメモリ領域を確保し、そのアドレスをロケータに格納して利用する。
  • データ構造へのアクセス: 連結リストやツリー構造など、ポインタでリンクされたデータ構造を扱う。
  • サブルーチン間でのデータ共有: サブルーチンに引数としてアドレスを渡し、共通のデータを操作する。

ADDR関数は、これらの高度なメモリ操作の基盤となるため、その特性と制約を正確に理解しておくことが、堅牢なメインフレームアプリケーション開発に繋がります。

ADDR関数と定数に関する制約は、一見すると些細な問題のように思えるかもしれませんが、その背景にあるメモリ管理の仕組みを理解することは、メインフレーム技術者として非常に重要です。今回解説した回避策や最適化の視点を、ぜひ日々の開発や移行作業に役立ててください。

コメント

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