【テクニカル・上級編】REPEAT関数の文字列複製とメモリ確保 – PL/Iの基本構文とデータ制御実践ガイド

はじめに:PL/Iにおける文字列複製の深層

メインフレームの現場において、PL/I(Programming Language One)ほど、その柔軟性と「コンパイラの懐の深さ」にエンジニアが唸らされる言語も珍しい。C言語のように厳格すぎず、COBOLのように冗長すぎない。しかし、その自由度の高さゆえに、言語仕様の裏側にあるメモリ管理のメカニズムを誤解していると、本番環境で突如としてS0C4(保護例外)やS0C7(データ例外)といった香ばしいアベンドを引き起こす。

今回取り上げるのは、ある文字列を指定回数だけ繰り返して結合する、いわゆる「文字列の動的複製(REPEAT処理的な操作)」である。

JavaやC#であれば `String.repeat()` や `StringBuilder` を一発叩いて終わり、という話なのだが、PL/Iの世界では、これを自前で、あるいは効率的なランタイムルーチンと連携して実装する必要がある。特に、基幹システムのバッチ処理で数万件規模のマスターデータを加工する際や、CICSオンラインの通信領域(COMMAREA)で動的なパディング生成を行う際、このメモリ確保とデータ転送の最適化を誤ると、たちまちCPU時間の高騰やストレージ不足を招くことになる。

今回は、IBM Enterprise PL/Iコンパイラの挙動、MVC(Move Characters)機械語命令への最適化、さらにはポインタとベース変数を用いた安全かつ高速な動的メモリ操作の極意について、現場の知見を総動員して解説しよう。

—

1. 予約語を持たない言語仕様と「ベース変数・ポインタ」の真価

PL/Iの最大の特徴の一つは、「予約語(Reserved Words)を持たない」という圧倒的な言語設計にある(厳体としては文脈依存キーワードはあるものの、COBOLのように `VALUE` や `DATE` だから変数名に使えない、といった縛りが極めて少ない)。そのため、極端な話、キーワードすら変数名として定義できてしまう。

この柔軟性は、データ制御や動的メモリ操作において強力な武器となる。特に、あらかじめ長さが確定していない可変長文字列や、REPEAT関数的な処理で動的にサイズが変動するバッファを扱う場合、`CONTROLLED` ストレージクラスや、ポインタ(POINTER)とベース変数(Based Variable)の組み合わせが不可欠となる。

以下に、ポインタとベース変数を用いた動的メモリ確保・解放の基本パターンを示す。

—————————————————————-

  • 任意の文字列を指定回数繰り返す動的メモリ操作のサンプル

—————————————————————-
REPEAT_STRING: PROC OPTIONS(MAIN);

DCL SRC_STR CHAR(10) VARYING INIT(‘ABC’); / 複製元文字列 /
DCL REP_COUNT FIXED BIN(31) INIT(5); / 複製回数 /

DCL OUT_LEN FIXED BIN(31); / 生成後の長さを格納 /
DCL P_BUF POINTER; / 動的バッファ用ポインタ /

/ ベース変数の定義:長さはポインタ経由で動的に決定される /
DCL DYNAMIC_BUF CHAR(32767) BASED(P_BUF);

/ 1. 必要な出力長を算出し、ストレージを動的取得 /
OUT_LEN = LENGTH(SRC_STR) REP_COUNT;

/ 最大長チェック(Enterprise PL/Iの限界や業務上の制約) /
IF OUT_LEN > 32767 THEN DO;
PUT SKIP LIST(‘エラー: バッファ長が許容値を超えています。’);
GOTO ERROR_RTN;
END;

/ ALLOCATE文による動的メモリ確保(ヒープ領域) /
ALLOCATE DYNAMIC_BUF CHAR(OUT_LEN) SET(P_BUF);

/ 2. 文字列の繰り返し配置とMVC最適化の適用 /
CALL BUILD_REPEATED_STRING(SRC_STR, REP_COUNT, OUT_LEN, P_BUF);

/ 3. 結果の確認(実際にはファイル出力やDB更新に渡す) /
PUT SKIP LIST(‘生成結果: ‘ || SUBSTR(DYNAMIC_BUF, 1, OUT_LEN));

/ 4. メモリリークを防ぐための確実な解放 /
FREE DYNAMIC_BUF;
GOTO NORMAL_EXIT;

ERROR_RTN:
/ 異常系処理 /
EXIT;

NORMAL_EXIT:
RETURN;

END REPEAT_STRING;

ここで重要なのは、`ALLOCATE` によって取得したヒープ上のメモリは、処理が終了しても明示的に `FREE` しない限り残り続けるという点だ。CICSオンラインのトランザクション内でこれをやり損なうと、いわゆる「ストレージ・リーク(Storage Leak)」を引き起こし、領域不足によるDFHSZ0002アベンドへと直結する。テックリードとしては、例外処理(ON CONDITION)を網羅し、異常終了時であっても確実に `FREE` が走る設計を義務付けなければならない。

—

2. コンパイラ最適化とMVC機械語命令の舞台裏

文字の結合や複写をループ処理で一文字ずつ行うような愚を犯すメインフレームエンジニアはいないと信じたいが、PL/Iにおいて文字列の代入や結合演算子(`||`)をどのように記述するかによって、生成される機械語(マシン語)の品質は劇的に変わる。

IBM Enterprise PL/Iコンパイラは、最適化レベル(`OPTIMIZE(2)` や `OPTIMIZE(3)`)を指定することで、連続したメモリ領域の転送に対してSystem/390アーキテクチャの真骨頂である MVC(Move Characters) 命令や MVCL(Move Characters Long) 命令をインライン展開、あるいはランタイムルーチン呼び出しへと自動最適化する。

効率的なバッファ構築サブルーチンの実装例

先ほどのプログラムから呼び出される、メモリブロック転送の最適化ロジックを見てみよう。

BUILD_REPEATED_STRING: PROC(P_SRC, P_CNT, P_LEN, P_PTR);

DCL P_SRC CHAR() VARYING;
DCL P_CNT FIXED BIN(31);
DCL P_LEN FIXED BIN(31);
DCL P_PTR POINTER;

DCL TARGET_AREA CHAR(P_LEN) BASED(P_PTR);
DCL I FIXED BIN(31);
DCL CUR_POS FIXED BIN(31);
DCL SRC_LEN FIXED BIN(31);

SRC_LEN = LENGTH(P_SRC);
CUR_POS = 1;

/

  • ループ内での効率的な文字列配置。
  • コンパイラは、このSUBSTRへの代入を高効率なMVC命令へ変換する。

/
DO I = 1 TO P_CNT;
SUBSTR(TARGET_AREA, CUR_POS, SRC_LEN) = P_SRC;
CUR_POS = CUR_POS + SRC_LEN;
END;

RETURN;
END BUILD_REPEATED_STRING;

コンパイラオプションの選定指針

基幹システムのバッチプログラムをコンパイルする際は、以下のオプション設定が鉄則となる。

  • `OPTIMIZE(2)` または `OPTIMIZE(3)`: ループのアンロールや、メモリブロック転送のMVC/MVCL最適化を最大限に引き出す。
  • `STGCT(CHECK)`: デバッグフェーズにおいて、ストレージの境界違反やオーバーレイを検知するために必須。
  • `TRAP(ON)`: ゼロ割りやポインタ不正などのハードウェア例外をPL/Iの条件として捕捉できるようにする。

もし最適化を怠り、非効率なバイト単位の処理を書いた場合、バッチウィンドウ(夜間バッチの処理時間枠)を確実に超過し、運用部門からの容赦ないエスカレーションを受けることなる。

—

3. 現場で遭遇するエッジケースとトラブルシューティング

レガシー移行や保守の現場では、テキスト通りにいかない「魔物」が潜んでいる。特に次のようなケースでは、ダンプ解析のスキルが問われる。

A. パックデシマル(FIXED DECIMAL)の内部符号反転バグとデータ例外(S0C7)

今回のテーマである文字列複製とは一見無縁に思えるかもしれないが、例えば「繰り返しの回数(`REP_COUNT`)」をマスターファイルから読み込んだパックデシマル(COMP-3)のまま、適切なキャストを行わずにループ制御や演算に用いた場合、上位・下位ニブルのゾーン部が不正(例:`X’0F’` 以外のパディング不正など)であると、データ例外(S0C7)が発生する。

PL/Iでは、型変換が自動で行われる(Implicit Conversion)ため便利である一方、外部ファイル(VSAMやDB2)からの入力データにゴミが入っていると、コンパイラが生成したパックデシマル演算命令で一発レッドカード(ABEND)となる。
対策として、入力直後に `VALID(VAR)` 組み込み関数を使用するか、あらかじめ `FIXED BINARY` へ安全にコンバージョンするガードロジックを挟むべきである。

B. 埋め込みSQL(DB2)およびCICS通信領域でのエッジケース

CICSオンラインで動的メモリ確保を行う際、EXEC CICS GETMAINで取得した領域をPL/Iのベース変数に割り当てるケースがある。この時、領域の長さ(LENGTH)計算を誤り、実際のMVC転送サイズがGETMAINのサイズを超過すると、ストレージの踏みつけ(Storage Overlays) が発生する。
これの何がタチが悪いかというと、エラーが発生したその瞬間ではなく、まったく関係のない後続のCICSタスクが異常終了(ASRA/S0C4アベンド)する という、原因特定が極めて困難な症状を引き起こすことだ。

ダンプ解析(IPCS等を用いたCEEDUMPやSVCダンプの解析)を行う際、不正なポインタアドレスの指す先がCICSのタスク共用領域(TWAやCSA)を破壊している痕跡を見つけたら、直前のPL/I側での文字列複製ロジックにおけるバッファ長計算ミスを疑うべきである。

—

4. Java / C# へのマイグレーション設計における注意点

レガシーマイグレーション(リライトやリファクタリング)のプロジェクトにおいて、PL/Iのこのような低水準なメモリ操作やポインタ・ベース変数を、JavaやC#のモダンなオブジェクト指向言語にどうマッピングするかは、アーキテクトの腕の見せ所である。

1. メモリ管理の抽象化:
PL/Iの `ALLOCATE` / `FREE` やポインタ操作は、JavaではGC(ガベージコレクション)に委譲されるため、メモリリークの心配は大幅に減る。しかし、大量の文字列結合を行うループで `String` 型を単純に `+` 演算子で結合していくと、内部で無数のインスタンスが生成されてヒープを圧迫し、Full GC頻発によるパフォーマンス劣化を引き起こす。Javaでは `StringBuilder`、C#では `StringBuilder` や `Span` / `Memory` を用いたバッファ再利用設計に書き換える必要がある。
2. 厳密な文字コード・長さの保証:
メインフレームのEBCDIC環境から、オープン系のUTF-8 / UTF-16環境へ移行する際、文字長(バイト数 vs 文字数)の概念の差に足元をすくわれる。PL/Iの `CHAR(n)` は基本的にバイト長(マルチバイト文字の場合はシフトコードの考慮も必要)であるが、Javaの `String` はUTF-16の文字数ベースである。文字列を複製・切り出しする際の外字(ユーザー定義文字)の扱いやパディング文字のパリティを含め、移行後の単体テストでは境界値テストを入念に行う必要がある。

—

おわりに

PL/Iの基本構文、そして予約語を持たない自由な設計思想の裏側には、ハードウェア(System/390アーキテクチャ)の特性を限界まで引き出すための合理的な理由と歴史がある。

「文字列を繰り返して複製する」という一見プリミティブな処理一つをとっても、動的メモリの確保、コンパイラの最適化、MVC命令の挙動、そしてCICSやDB2といった周辺ミドルウェアとの連携を意識できるか否かで、システム全体の信頼性は大きく変わる。

レガシーシステムのモダナイゼーションを推進するテックリードとして、言語の表面的なしみったれた書き換えに終始するのではなく、そのコードがハードウェア上でどのように実行されるのかという「原点」を常に頭に置きながら、堅牢かつ洗練されたアーキテクチャ設計を貫いていってほしい。

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