【実務・中級編】STRING関数による構造体から文字列へのキャスト – PL/Iの基本構文とデータ制御実践ガイド

おい、最近の若手はCOBOLから入ることが多いから、いざPL/Iの保守現場に放り込まれると「なんじゃこりゃ」と頭を抱えることが多いらしいな。特に、構造体を丸ごとファイルに吐き出したり、逆に電文をごっそり受け取ったりする処理に直面したときだ。

「あれ? COBOLみたいに `MOVE CORRESPONDING` や `REDEFINES` みたいな小細工をしなくても、なんでこの先輩のコードは構造体をそのまま1本の文字列として扱えているんだ?」ってな。

いいか、よく聞いてくれ。PL/IにはCOBOLのような「予約語の縛りによるガチガチの構文規則」とは一味違う、独特の懐の深さ――いや、一歩間違えると大怪我をする「強力すぎる機能」がある。その代表格が、今回解説する `STRING` ビルトイン関数 による構造体から文字列へのキャストだ。

今日のテーマは、この `STRING` 関数がメモリ上で何をやっているのか、そしてレガシーシステムの現場でどう安全に使いこなすべきか、俺が骨の髄まで叩き込んでやる。

—

1. 予約語を持たないPL/Iの懐の深さと、今回の核心

まず大前提として、PL/IにはC言語やCOBOLのような「絶対に変数名に使ってはいけない予約語(Reserved Words)」が、実は文脈依存を除いてほぼ存在しない。極端な話、`IF` や `READ` さえもプログラマが変数名として定義できてしまう(コンパイラは前後の文脈から判断するが、そんな狂った真似は絶対にやっちゃいけないぞ)。

この「自由度の高さ」は、データ構造の定義においても猛威を振るう。特に、親子関係を持つ構造体(Structure)を定義したとき、PL/Iコンパイラはそれをメモリ上でどう配置しているか知っているか?

通常、構造体のメンバーは宣言順にメモリ上に並ぶが、データ型の境界調整(アライメント、例えば半ワードや全ワード境界へのパディング)が挟まることがある。この「パディングの罠」を無視して構造体をまるっとバイト列として扱おうもんなら、夜間バッチで突如としてデータ化けを起こし、運用担当者から血の涙を流す電話がかかってくることになる。

そこで登場するのが、`STRING` ビルトイン関数だ。これを使えば、ネストした複雑な構造体であっても、一瞬で「ただの連続した文字の塊(CHARACTER)」にキャストできる。

—

2. STRING関数によるメモリ上の連続性確保とアドレス変換

`STRING(構造体名)` を記述すると、コンパイラは何をしているのか?

内部的には、コンパイラは指定された構造体(およびその下位のサブ構造体)が占有するメモリ領域の先頭アドレスと全長(バイト数)を算出し、それをあたかもひとつの `CHARACTER VARYING` または固定長の `CHARACTER` 文字列であるかのように再解釈(Re-interpretation)させるコードを生成する。

ただし、ここでベテランとしての注意点を一つ。
構造体の中に不連続なポインタ変数や、余計なアライメント調整によるパディングが含まれている場合、`STRING` で展開した文字列の長さは、単純な各メンバーの `LENGTH` の単純合計とは一致しないことがある。

だからこそ、VSAMファイルへのレコード出力や、TCP/IPソケット経由での電文送受信といった「バイナリレベルの厳密さが求められる処理」では、構造体の定義に `UNALIGNED`(アンアライメント)属性 を明示することが鉄則だ。これをつけることで、コンパイラに「パディングなしで隙間なくメモリを詰めろ」と指示できる。

—

3. 実践!VSAMファイル入出力とONユニットを伴う堅牢なコード例

口で言うだけじゃ信用しないだろうからな。実際のメインフレームの夜間バッチを想定した、実用的なPL/Iのソースコードを見せてやろう。

このサンプルは、顧客マスタの構造体を `STRING` 関数で文字列表現にキャストし、VSAMのKSDS(キー順データセット)へ出力する処理だ。さらに、万が一の入出力エラーに備えて `ON` ユニットによる例外処理も組み込んでいる。

1
—————————————————————-

  • 顧客マスタ構造体をSTRING関数で文字列化しVSAMへ書き込むサンプルバッチ

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

DCL 1 CUST_REC UNALIGNED, / 隙間なくメモリに配置 /
5 CUST_ID CHAR(5), / 顧客ID /
5 CUST_NAME CHAR(20), / 顧客名 /
5 CUST_INFO UNALIGNED,
10 REG_DATE CHAR(8), / 登録日 (YYYYMMDD) /
10 STATUS CHAR(1), / ステータス (0:無効 1:有効) /
5 BALANCE PIC ‘99999999V99’ BIN; / 残高 (バイナリ数値) /

DCL OUT_AREA CHAR(36); / 構造体トータル長と一致させる /
DCL VSAM_FILE FILE RECORD OUTPUT;
DCL IO_ERR_FLAG BIT(1) INIT(‘0’B);

————————————————————

  • ONユニットによるファイル入出力異常の捕捉

————————————————————
ON ERROR
BEGIN;
DISPLAY(‘【SEVERE ERROR】予期せぬ例外が発生しました。処理を中断します。’);
IO_ERR_FLAG = ‘1’B;
GOTO ABEND_ROUTINE;
END;

ON ENDFILE(VSAM_FILE)
BEGIN;
DISPLAY(‘ファイル終端に到達しました。’);
END;

  • ファイルオープン

OPEN FILE(VSAM_FILE) ENVIRONMENT(WBAM); / WBAM等は環境に応じる /

  • データの初期設定

CUST_ID = ‘A0012’;
CUST_NAME = ‘山田 太郎 ‘;
REG_DATE = ‘20231025’;
STATUS = ‘1’;
BALANCE = 1250000; / バイナリ値の設定 /

————————————————————

  • [核心] STRING関数による構造体の文字列キャスト
  • 構造体全体を一つの文字変数(OUT_AREA)に一括転送する

————————————————————
OUT_AREA = STRING(CUST_REC);

  • VSAMファイルへレコード出力

WRITE FILE(VSAM_FILE) FROM(OUT_AREA);

IF IO_ERR_FLAG THEN
GOTO ABEND_ROUTINE;

DISPLAY(‘正常終了:顧客ID = ‘ || CUST_ID);

CLOSE FILE(VSAM_FILE);
RETURN;

ABEND_ROUTINE:
DISPLAY(‘異常終了ルーチンを通過しました。シスログを確認してください。’);
SIGNAL ERROR;

END CUST_BCH;

コードの解説と現場のツボ

1. `UNALIGNED` の指定
構造体の定義に `UNALIGNED` をつけている点に注目しろ。これを忘れると、コンパイラが勝手に境界調整用のパディングバイトを挿入し、`STRING(CUST_REC)` で取り出される文字列のサイズが想定(36バイト)より大きくなってしまい、VSAM側のレコード長エラー(FILE STATUSの不一致など)を引き起こす。
2. `STRING(CUST_REC)` の威力
個別のフィールドを一つずつ連結しなくても、この1行だけで階層構造を持ったレコード全体がフラットな `OUT_AREA` に変換される。電文のログ出力や、ダンプ採取時のメモリイメージ作成にはこれ以上ないほど強力だ。
3. `ON` ユニットの活用
メインフレーム開発ではお馴染みの `ON ERROR` や `ON ENDFILE` だ。PL/Iはエラーが発生した際、C言語のように毎回戻り値のステータスコードを愚直にチェックするだけでなく、言語処理系レベルで例外をトラップして制御フローを安全に切り替えることができる。

—

4. 先輩からの現場アドバイス:デバッグと移行時の注意点

最後に、今後マイグレーションや大規模改修でこの手のコードを触る際の「生きた知見」を授けておく。

  • 文字コード(EBCDIC vs ASCII)の壁に気をつけろ

もし将来的にメインフレームからオープン系(LinuxやWindows)へPL/Iごと移行する、あるいはデータを連携する際、`STRING` でキャストしたバイナリ文字列の中に `FIXED BIN` や `PIC` の数値データが混ざっていると、EBCDICとASCIIの文字コード差異だけでなく、バイトオーダー(エンディアン)の壁にブチ当たる。ビッグエンディアン前提で組まれたバイナリキャストは、リトルエンディアン環境ではそのままでは動かない。

  • ダンプを読むときはレイアウトマップを手元に

スナップダンプやSYSUDUMPを解析する際、`STRING` で固められたエリアがメモリ上でどう並んでいるか、コンパイルリストの「Storage Map(ストレージ・マップ)」を必ず突き合わせろ。オフセットが何バイトズレているか一発でわかるはずだ。

PL/Iの `STRING` 関数は、正しく使えばこれほどエレガントで強力な武器はない。しかし、一歩間違えばメモリ破壊という魔物を呼び起こす両刃の剣だ。構造体のレイアウトとメモリの連続性を頭に叩き込み、自信を持ってコードを書けるようになってくれ。期待しているぞ。

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