おい、最近オンラインからバッチに流れてきた若手が「PL/Iって変数の予約語がないから気持ち悪い」「VARYING項目の長さってどうやって裏で持ってるんですか?」なんて質問を持ってきたんだ。
いいか、よく聞いてくれ。
俺たちが日々向き合っているIBMメインフレームの基幹システム、その何十年もの歴史を支えてきたPL/Iという言語は、表面的なお行儀の良さよりも「マシン語への直結と圧倒的な表現力」を重視して設計されている。C言語やJavaのように「この単語は変数名に使っちゃダメよ」という予約語(Reserved Words)の概念がPL/Iには原則として存在しない。
コンパイラは文脈(Context)から「あ、ここは変数名だな」「ここは組み込み関数名だな」と見抜いている。だからこそ、ちょっとしたコーディングの油断がとんでもないコンパイルエラーや、本番稼働中のストレージ異常を引き起こす。
今回はその中でも、現場のトラブルシューティングや他言語からのマイグレーション時に必ずと言っていいほど躓く、「VARYING属性を持つ可変長文字列の裏側(記述子:Descriptor)」と、それに密接に関わるLENGTH関数の挙動について、俺の経験を踏まえて徹底的に叩き込んでやろう。
—
1. VARYING文字列の内部構造と「記述子(Descriptor)」の正体
まず、PL/Iで `DCL WK_MSG CHAR(100) VARYING;` と書いたとき、メインフレームのメモリ上でこの変数がどう保持されているかイメージできるか?
固定長の `CHAR(100)` であれば、単に100バイトの連続した領域が確保されるだけだ。しかし、`VARYING` をつけると、コンパイラはデータ領域の直前に「現在の実際の文字長」を格納するための領域を自動的に付加する。これが、今回焦点を当てる記述子(厳密には長さプレフィックス)の正体だ。
- 構造のイメージ:
- 先頭の 2バイト(半ワード・BINARY FIXED(15)相当):現在の文字列長(文字数)
- それに続く領域:実際の文字列データ(最大長までの領域が確保されるが、使われるのは長さ分だけ)
ここでベテランエンジニアなら誰しも一度はハマる罠がある。
「あれ?じゃあ、VSAMファイルへの書き込みや、ポインタ操作でこのVARYING項目を直接バイナリとして扱ったらどうなる?」
そう、LENGTH関数を使わずに生データをそのまま出力すると、先頭の2バイトの長さ情報(2進数)が文字化けしたゴミデータとしてレコードに混入してしまうのだ。
—
2. LENGTH関数の真の挙動:固定長と可変長の違い
ここで `LENGTH` 組み込み関数の話をしよう。
多くのプログラマは「文字列の長さを返す便利な関数」としか思っていないが、PL/Iにおける `LENGTH` の挙動は、対象の変数が固定長(FIXED)か可変長(VARYING)かによって、コンパイラの生成するコードが劇的に変わる。
1. 固定長 `CHAR(n)` の場合:
- コンパイル時にサイズが確定しているため、`LENGTH(変数名)` は定数(Literal)として扱われる。実行時にわざわざ長さを数えに行ったりはしない。
2. 可変長 `CHAR(n) VARYING` の場合:
- 実行時の状態によって長さが変わるため、コンパイラは変数本体の直前にある先頭2バイトの記述子を参照するコードを自動生成する。
つまり、俺たちがコード上で何気なく書いている `W_LEN = LENGTH(WK_VARYING_STR);` という一行は、裏で「変数ポインタからマイナス2バイト(あるいはコンパイラが管理するオフセット)を覗きに行って、そこに入っている長さを取得する」という処理に翻訳されているんだ。
—
3. 実践!VSAM入出力とONユニット、記述子参照を網羅したPL/Iコード例
百聞は一見にしかずだ。実際のバッチ処理を想定した、実用的なPL/Iのソースコードを見てほしい。
ここでは、VSAM(KSDS)からのレコード読み込み、VARYING項目の正確な長度の取得、そして万が一のデータ異常に備えたONユニット(例外処理)の制御フローを組み込んでいる。
1
—————————————————————-;
- 修正履歴: 202X/10/15 新規作成 ;
- 概要 : VARYING文字列の記述子参照とVSAM入出力のサンプル ;
—————————————————————-;
MBR_SAMPLE: PROC OPTIONS(MAIN);
/ — 宣言部 — /
DCL VSAM_IN FILE RECORD INPUT;
/ VSAMレコード様式(可変長風のデータを想定) /
DCL 1 VSAM_REC,
5 REC_LEN FIXED BIN(15), / レコード長 /
5 REC_BODY CHAR(200); / データ本体 /
/ ワーキング変数 /
DCL W_MSG_VAR CHAR(150) VARYING; / 可変長文字列 /
DCL W_REAL_LEN FIXED BIN(15); / 実際の文字長保存用 /
DCL EOF_FLG CHAR(1) INIT(‘OFF’); / 終了フラグ /
/ — ONユニット(例外・割り込み処理の定義) — /
/ ファイル読み込み時の終了条件(ENDFILE)をトラップ /
ON ENDFILE(VSAM_IN)
BEGIN;
EOF_FLG = ‘ON’;
DISPLAY(‘ ℹ️ INFO: VSAMファイルがEOFに達しました。 ‘);
END;
/ データ例外(数値変換エラーや領域溢れなど)のトラップ /
ON ERROR
BEGIN;
DISPLAY(‘ ❌ ERROR: 予期せぬシステム例外が発生しました。 ‘);
/ 異常終了コードをOSに返してストップ /
SIGNAL FINISH;
END;
/ — 処理開始 — /
DISPLAY(‘ 🚀 処理開始: MBR_SAMPLE STARTED ‘);
/ VSAMファイルのオープン /
OPEN FILE(VSAM_IN);
/ 初回レコード読み込み /
READ FILE(VSAM_IN) INTO(VSAM_REC);
DO WHILE(EOF_FLG = ‘OFF’);
/ レコード本体から可変長ワークへ転記 /
/ この代入時に、W_MSG_VARの先頭記述子(2バイト)に長さが自動設定される /
W_MSG_VAR = SUBSTR(REC_BODY, 1, 80);
/ ========================================================= /
/ 【核心】LENGTH関数による記述子(プレフィックス)の参照 /
/ ========================================================= /
/
- ここでLENGTH関数を呼ぶと、コンパイラはW_MSG_VARの裏側にある
- 記述子を安全に参照し、現在の有効文字長を取り出す。
/
W_REAL_LEN = LENGTH(W_MSG_VAR);
/ デバッグ用ディスプレイ出力(実務ではSYSOUTへログ出力) /
DISPLAY(‘有効文字長 = ‘ || TRIM(CHAR(W_REAL_LEN)) ||
‘ / 内容 = ‘ || W_MSG_VAR);
/ 次のレコード読み込み /
READ FILE(VSAM_IN) INTO(VSAM_REC);
END;
/ — 終了処理 — /
CLOSE FILE(VSAM_IN);
DISPLAY(‘ 🏁 処理正常終了: MBR_SAMPLE ENDED ‘);
RETURN;
END MBR_SAMPLE;
—
4. ベテランから現場への教訓・デバッグのコツ
このコードと内部構造を理解していれば、今後お前たちが遭遇するであろう以下のトラブルを華麗に回避できるはずだ。
1. ポインタや基底変数(BASED)を使ったハックを行う際の注意
万が一、性能チューニングや特殊なレコード構造の解析のために、`BASED` 変数を使ってストレージを直接マッピングするアプローチを取る場合、`VARYING` 項目の先頭2バイトをうっかりデータの一部として読み飛ばしたり、逆に巻き込んでしまったりするバグが多発する。
「VARYINGを使うなら、素直に言語仕様(LENGTH関数)に長さを計算させろ」。これが鉄則だ。コンパイラを出し抜こうとして低水準なポインタ計算を自前で行うと、保守フェーズで後任者が地獄を見る。
2. 他言語(COBOL等)とのデータ連携
COBOLには純粋な `VARYING` 型はない(通常は `USAGE IS COMP` の長さフィールド + 文字列の複合構造体で自前定義する)。PL/Iで作成した `VARYING` 項目をそのまま外部ファイル(BSAMやQSAM)へレコードとして吐き出し、それをCOBOLで読もうとすると、先頭の2バイトのバイナリ長が邪魔をしてデータがズレる。
外部インターフェースの設計では、`VARYING` ではなくあえて固定長 `CHAR` を使うか、明示的に長さフィールドとデータ本体を分離した構造体(SOP:Start of Paragraph ではない、構造化レコード)で定義するのがプロの作法だ。
基幹システムのコードは、ただ動くだけではダメだ。裏側でメモリがどう動き、コンパイラがどう解釈しているかという「構造のリアリティ」を持てるかどうかが、三流エンジニアと一流アーキテクトを分ける境界線になる。
今日の話をしっかりと頭に叩き込んで、次のバッチ改修も完璧に決めてくれよ。期待しているぞ!
