おい、最近入ってきた若い連中が「PL/Iって変数が予約語とかぶっても怒られないから楽勝ですね」なんて笑いながらコードを書いているのを見かけてな、思わず背筋が凍る思いをしたんだ。
おいおい、ちょっと待てと。確かにPL/IにはCOBOLのような厳格な「予約語(Reserved Words)」という概念がほぼ存在しない。極端な話、`IF`や`THEN`さえも変数名として定義できてしまう自由度の高さがある。だがな、「コンパイラが怒らないからといって、人間が混乱しないわけではない」し、何より裏で動くメインフレームのアーキテクチャやメモリ管理の仕組みを無視したコーディングをしていると、深夜のバッチ異常終了(S0C4やS806といったお馴染みの地獄)で泣きを見るのはこっちなわけだ。
今日は、そんなPL/Iの懐の深さと、一歩間違えるとシステムをクラッシュさせる危険性を秘めた「REPEAT関数の文字列複製と動的メモリ確保の裏側」について、現場のノウハウを叩き込んでやろうと思う。
—
1. 予約語なき世界:PL/Iの識別子規則とコンパイラの苦悩
まず大前提として、PL/Iの識別子は最大31文字(最新のEnterprise PL/Iであればもっといけるが実務では31文字以内が安全圏だ)、英数字とブレンド(`_` アンダースコア)で構成される。
なぜPL/Iには予約語がないのか?
それは、コンパイラが「文脈(Context)」でキーワードを判断しているからだ。例えば、`IF`という文字が出てきても、それが変数名なのか条件分岐の命令なのかを前後の構文解析(パース)で勝手に判断してくれる。
しかし、現場でこれを悪用(?)して、以下のようなコードを書くやつがいる。
DECLARE IF FIXED BIN(31); / 最悪な命名規則 /
こんなコードを見かけたら、翌朝のコーヒーを奢る代わりにこっそり修正パッチを書かせてやりたくなる。コンパイラは文句言わずに通すが、コードレビューをするこっちは目がチカチカして脳がバグる。識別子は「意味のある英語、かつ保守者が絶望しない名前」をつけるのが鉄則だ。
—
2. REPEAT関数の罠:文字列複製と動的メモリ確保のリアル
さて、本題に入ろう。文字列を特定の回数だけ繰り返して新しいワークエリアを作りたい時、君たちはどう書いている?
COBOLであれば `INSPECT` や `STRING` のループで泥臭く実装するところだが、PL/Iには強力な組み込み関数(BUILTIN)である `REPEAT` が用意されている。
……と言いたいところだが、ここで重大な勘違いをしているプログラマが多い。
PL/Iの `REPEAT(x, n)` は、実は「文字列をn回繰り返す関数」ではなく、「引数xをn回繰り返した配列(あるいは連結)」を作るためのものだ。文字列操作で私たちが期待する「特定の文字列を指定回数結合した可変長文字列」を作るには、文字の切り出しや長さの制御、そして何より動的なメモリ確保(STORAGE MANAGEMENT)を意識しなければならない。
特に、メインフレームのVSAMファイルやレコード入出力のバッファ構築において、可変長文字列をゴリゴリ動的に生成する処理は、S0C4(ストレージ保護例外)の温床になりやすい。
メモリの動的割り当てとMVC命令の最適化
PL/Iで動的メモリを扱う場合、`ALLOCATE` ステートメントとポインタ変数(`POINTER`)を駆使する。
OS/390やz/OSのアーキテクチャ上、メモリ間のデータ転送はハードウェア命令である MVC(Move Characters) が裏で高速に実行されるようコンパイラが最適化(OPTIMIZE)をかける。
しかし、あらかじめ確保するサイズ(LENGTH)を見誤ったり、`EXPLICIT` な解放(`FREE`)を忘れてリソースリークを起こしたりすると、夜間バッチの後半で領域枯渇を起こし、運用担当者から血祭りにあげられることになる。
—.
3. 実践コード:VSAMレコード生成とREPEAT関数的処理の実装例
百聞は一見にしかずだ。実際の業務バッチを想定したサンプルコードを見せよう。
このコードは、入力されたマスターのコードに対し、指定されたパディング文字を指定長まで繰り返し結合した「フォーマット済み出力レコード」を動的に生成し、VSAM(KSDS)へ書き込む一連の流れを模したものである。
/==================================================================/
/ PROGRAM-ID: RPT001PL /
/ DESCRIP : 動的メモリ確保と文字列構築によるVSAMレコード出力最適化 /
/==================================================================/
RPT001PL: PROC OPTIONS(MAIN);
/ コンパイラオプションおよび組み込み関数宣言 /
DCL SYSIN FILE INPUT;
DCL OUTVSAM FILE RECORD OUTPUT VSAM;
/ ワーク変数定義 /
DCL W_IN_CODE CHAR(10) INIT(‘ABCDE’); / 入力コード /
DCL W_PAD_CHAR CHAR(1) INIT(‘-‘); / パディング文字 /
DCL W_REP_CNT FIXED BIN(31) INIT(5); / 繰り返し回数 /
DCL W_OUT_LEN FIXED BIN(31); / 生成長 /
/ 動的文字列制御のためのポインタとベース変数 /
DCL P_WORK_AREA POINTER;
DCL D_WORK_STR CHAR(32767) BASED(P_WORK_AREA) VARYING;
/ VSAM出力レコード定義 /
DCL 1 OUT_REC,
5 REC_KEY CHAR(10),
5 REC_DATA CHAR(100);
/ ファイルオープン /
OPEN FILE(OUTVSAM) OUTPUT;
/ 1. 繰り返し文字列の動的生成(メモリ確保) /
/ 実際のビジネスロジックではここで可変長領域のサイズを計算する /
W_OUT_LEN = LENGTH(W_IN_CODE) + (W_REP_CNT LENGTH(W_PAD_CHAR));
IF W_OUT_LEN > 100 THEN
GOTO ERROR_ROUTINE;
/ 必要なサイズ分のストレージを動的に獲得(OSのGETMAINを発火) /
ALLOCATE D_WORK_STR SET(P_WORK_AREA);
/ 2. 文字列の組み立て(ビルトイン関数と連結演算子 || の活用) /
/ ※REPEAT関数の厳密な挙動に頼らず、安全に文字列を拡張するロジック /
D_WORK_STR = W_IN_CODE;
DO I = 1 TO W_REP_CNT;
D_WORK_STR = D_WORK_STR || W_PAD_CHAR;
END;
/ 3. VSAMレコードへの値設定(ハードウェアMVC命令による転送効率化) /
REC_KEY = W_IN_CODE;
REC_DATA = D_WORK_STR; / BASED変数から固定長領域への転送 /
/ 4. VSAM(KSDS)への書き込みとONユニットによる例外監視 /
ON ERROR BEGIN;
PUT SKIP LIST(‘ ERROR: VSAM WRITE FAILED FOR KEY = ‘, REC_KEY);
GOTO ERROR_ROUTINE;
END;
WRITE FILE(OUTVSAM) FROM(OUT_REC);
/ 5. 忘れちゃいけない動的ストレージの解放(FREE) /
FREE D_WORK_STR;
CLOSE FILE(OUTVSAM);
RETURN;
ERROR_ROUTINE:
IF P_WORK_AREA ^= NULL() THEN
FREE D_WORK_STR;
PUT SKIP LIST(‘ FATAL: PROGRAM ABNORMALLY TERMINATED.’);
CLOSE FILE(OUTVSAM);
SIGNAL ERROR;
END RPT001PL;
—
4. ベテランからの現場の教訓(デバッグのコツ)
どうだ、このコードのキモが分かったか?
1. BASED変数とALLOCATEのペアはセットで管理しろ
`ALLOCATE` を使ったら、処理の最後、あるいは異常終了(`ERROR_ROUTINE`)の必ず手前で `FREE` を通すこと。これをサボると、長期間稼働するオンラインシステムや巨大バッチでストレージリーク(メモリリーク)を起こし、領域不足でシステム全体が巻き添え食う。
2. ONユニットで例外をコントロールしろ
VSAMへの書き込みや入出力エラーは、ただコンパイラ任せにするな。`ON ERROR` や `ON UNDEFINEDFILE` などの条件監視(ON-units)を適切に張り、異常時でも必ずメモリの解放とファイルのクローズを行わせるのが「プロの仕事」だ。
3. ハードウェアの挙動(MVC)を意識した文字数制限
PL/Iの文字列結合 `||` は非常にスマートに見えるが、裏では一時領域の確保と文字移動(MVC)が走っている。ループ内で無駄に何回も文字列を結合すると、CPU時間を無駄に喰いつぶす(CPUハイパフォーマンストランザクションの敵だ)。文字数が固定、あるいはあらかじめ予測できるのであれば、一度にメモリを確保してパディングする設計にしろ。
メインフレームの寿命は長い。そして、そこで動くPL/Iのコードもまた、次の世代に引き継がれていく。
「動けばいいや」ではなく、「なぜこのメモリ確保が必要なのか」「なぜこの予約語のない言語でこの命名をしたのか」を語れるエンジニアになってくれ。期待しているぞ。
