【PL/I深層】SUBSTR関数の魔力と、メモリオーバーレイという「見えない爆弾」
若手エンジニア諸君、今日もメインフレームの海を泳いでいるか。
基幹システムの保守をしていると、避けては通れないのが文字列操作だ。特に`SUBSTR`関数。これほど便利で、かつ使い方を誤るとシステムを奈落の底へ突き落とす関数も珍しい。
今日は、IBMメインフレームにおける`SUBSTR`の真実と、それが引き起こす「メモリ・オーバーレイ」という悪夢について、現場の視点から解説する。
—
1. SUBSTR関数の本質:メモリを「切り出す」のではなく「指す」
まず、PL/Iの`SUBSTR`を単なる「文字列のコピー関数」だと思っているなら、今すぐその認識を改める必要がある。
`SUBSTR(STRING, START, LENGTH)`という構文は、内部的には対象となる変数(`STRING`)のメモリ上の開始アドレスにオフセットを加え、そこから指定された長さの領域を「ターゲット」として参照させるものだ。
もし、この`SUBSTR`を代入文の左辺に使った場合、どうなるか?
/i
/ 安全な使い方の例:固定長文字列の特定位置を書き換える /
DCL WORK_AREA CHAR(80) INIT(‘ ‘);
SUBSTR(WORK_AREA, 10, 5) = ‘ABCDE’;
このコードは極めて健全だ。`WORK_AREA`という確保済みの領域の、10バイト目から5バイト分を上書きしている。しかし、ここに「魔の境界」が存在する。
—
2. 境界外アクセス(オーバーレイ)の危険性
ベテランの我々が最も恐れるのは、`SUBSTR`の第3引数(長さ)が動的に計算される際、意図せず定義された領域を超えてしまうケースだ。
例えば、VSAMファイルから読み込んだレコードのフィールドを加工する際、以下のコードを見てほしい。
/i
DCL RECORD_BUFFER CHAR(100);
DCL OFFSET FIXED BIN(15);
DCL LEN FIXED BIN(15);
/ VSAM読み込み処理等は省略 /
/ … /
/ 外部からの入力値が想定を超えていた場合 /
IF OFFSET + LEN > 100 THEN DO;
/ ここでエラー処理をしないと、隣接する変数を破壊する /
SIGNAL CONDITION(SUBSCR_ERR);
END;
SUBSTR(RECORD_BUFFER, OFFSET, LEN) = ‘OVERWRITE’;
もし`OFFSET + LEN`が100を超えたまま実行されると、どうなるか。コンパイラは「お前がそうしろと言ったのだから」と、`RECORD_BUFFER`の後続メモリ領域(例えば、運悪く隣接する重要フラグや、ポインタ変数など)を平然と上書きする。
これが「オーバーレイ」だ。
ダンプを見ても、なぜ値が化けたのか直前まで判明しない。ABENDしないまま異常なデータがVSAMに書き込まれ、翌日のオンライン処理で致命的なエラーを引き起こす。これが、メインフレームのデバッグで最も胃が痛くなる瞬間である。
—
3. 実践:安全な文字列処理のための「ONユニット」とチェック
では、どう防ぐか。まずは`SUBSCRRG`(Subscript Range)オプションをコンパイル時に付与すること。そして、重要なデータ操作の前には必ず境界チェックを行う。
以下に、現場で使える「安全なレコード操作」のテンプレートを置いておく。
/i
/ 安全な切り出しと代入のサンプル /
PROG_MAIN: PROC OPTIONS(MAIN);
DCL MSG_BUF CHAR(50) INIT(‘SYSTEM_READY_001’);
DCL TRGT_POS FIXED BIN(15) INIT(13);
DCL TRGT_LEN FIXED BIN(15) INIT(3);
/ 文字列操作を保護するブロック /
BEGIN;
DCL (P, L) FIXED BIN(15);
P = TRGT_POS;
L = TRGT_LEN;
/ 境界チェック:領域外を触ろうとしていないか厳密に判定 /
IF (P > 0) & ((P + L – 1) <= LENGTH(MSG_BUF)) THEN
SUBSTR(MSG_BUF, P, L) = '999';
ELSE
PUT SKIP LIST('警告: 境界外アクセスを阻止しました');
END;
PUT SKIP LIST('RESULT:', MSG_BUF);
END PROG_MAIN;
---
4. アーキテクトからの助言:デバッグの極意
もし、原因不明の変数破壊に直面したら、以下の手順を試してほしい。
1. コンパイラオプションの見直し: `CHECK`や`SUBSCRRG`を有効にして再コンパイルせよ。バッチの実行時間は伸びるが、犯人は一発で特定できる。
2. ストレージ・ダンプの確認: 破壊された変数の直前のメモリ位置を特定し、その領域を触っている`SUBSTR`の引数を一つずつ追跡する。
3. `DEFINED`属性の罠: `DCL A CHAR(10), B CHAR(10) DEFINED A;`のような定義をしている場合、`A`への操作が`B`を書き換えるのは仕様だが、意図しないオーバーレイと混同しやすい。構造体マッピングを再確認しろ。
PL/Iは、メモリを直接叩ける強力な剣だ。しかし、剣の扱いを間違えれば自分を傷つける。
コードを書くときは常に「このメモリの持ち主は誰か?」を意識すること。それが、大規模マイグレーションや複雑なバッチ改修を生き抜く、唯一の道だ。
また何か壁にぶつかったら来い。次回のコードレビューで待っている。
