【実務・中級編】CHARACTER属性の内部表現とアライメント – PL/Iの基本構文とデータ制御実践ガイド

おい、最近入ってきた若手が「PL/Iって変数が多すぎて訳が分かりません」「なんでこんなに変なところでメモリがズレるんですか」って泣きついてきたんだ。気持ちはよく分かる。C言語やJavaに慣れた頭でメインフレームの基幹系コードを覗くと、PL/Iの独特な空気感、特にデータ制御の泥臭さに面食らうよな。

今日はな、その中でも「CHARACTER属性の内部表現とアライメント(境界調整)」について、俺が現場で散々痛い目を見て叩き込んできたノウハウを包み隠さず伝授しようと思う。

VSAMのレコード入出力で「なぜかデータが化ける」「期待したオフセットにならない」と深夜の障害対応で冷や汗をかいた経験があるなら、今日の話は間違いなく宝物になるはずだ。気合を入れてついてこいよ。

—

1. 予約語を持たないPL/Iの「懐の深さ」と、それに伴う落とし穴

まず大前提として、PL/IにはC言語のような「厳格な予約語(Reserved Words)」が存在しない。例えば、`IF` や `THEN` さえも、コンテキストによっては変数名として使えてしまう。これ、柔軟性としては最高なんだけど、一歩間違えると保守フェーズで地獄を見る原因になる。

そして今回焦点を当てる CHARACTER属性(固定長文字列) だ。例えば `DCL EMP_NAME CHAR(20);` と定義した場合、メモリ上でこいつらがどう並んでいるか、正確にイメージできているか?

単に「20バイトの領域が確保されるんでしょ」で済ませているうちは、中級者にはなれない。メインフレームのハードウェア(Zアーキテクチャ)の特性上、CPUがメモリを効率よく読み書きするためには「アライメント(境界調整)」という避けて通れないルールが存在するんだ。

—

2. CHARACTER属性のメモリ配置とアライメントの基本

PL/Iの固定長文字列(`CHAR(n)`)は、基本的には境界調整を意識させない連続した領域として配置される。しかし、これが構造体(`STRUCTURE`)のメンバとなったり、他言語(COBOLやC)とのデータ連携、さらにはVSAMのレコード構造と絡むと話が変わってくる。

ハードウェアの境界(Boundary)の現実

IBMメインフレームのCPUは、2バイト(半ワード)、4バイト(フルワード)、8バイト(ダブルワード)といった境界にデータが綺麗に揃っていると、アクセスが爆速になる。逆に、境界がズレた(アンアラインドな)データを無理やり読み込もうとすると、CPU内部で追加のサイクルが発生し、バッチ処理全体のスループットが確実に劣化する。

構造体の中で `CHAR` 型の直後に `FIXED BINARY(31)`(4バイトの数値)などを定義した場合、コンパイラはパフォーマンスを最適化するために、自動的に「パディング(隙間)」を挿入する。これを理解していないと、VSAMファイルや外部記憶装置に書き出した際の物理レコード長が、設計書の想定とズレてしまうという致命的なバグを生むんだ。

—

3. 実践コード:アライメントとVSAM入出力を考慮したデータ構造

百聞は一見に如かずだ。実際のバッチプログラムを想定したコードを見てみよう。ここでは、社員マスタ(VSAM KSDS)を読み込み、構造体のアライメントを意識しながらデータを処理する実用的なサンプルを示す。

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

  • プログラム名: EMPLPR01
  • 概要 : VSAMファイルから社員データを読み込み、
  • アライメントを考慮した構造体で処理するバッチ

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

/ — 1. 外部ファイル(VSAM)の定義 — /
DCL EMP_FILE FILE RECORD
ENV(VSAM ORGANIZATION(KEYED))
ACCESS(SEQUENTIAL);

/ — 2. 処理制御用フラグ — /
DCL EOF_FLG CHAR(1) INIT(‘0’);
DCL IO_STATUS FIXED BIN(15,0);

/ — 3. 社員レコード構造体(アライメントの明示的制御) — /
/ ALIGNED属性を指定し、ハードウェア境界に綺麗に配置させる /
Dcl 1 EMP_RECORD ALIGNED,
5 EMP_ID CHAR(6), / 社員ID(6バイト) /
5 FILLER_1 CHAR(2), / 境界調整用のパディング /
5 EMP_NAME CHAR(30), / 氏名(30バイト) /
5 DEPT_CODE CHAR(4), / 部署コード(4バイト) /
5 BASE_SALARY FIXED BIN(31); / 基本給(フルワード数値) /

/ — ONユニットによる入出力例外(エラー)の捕捉 — /
ON ENDFILE(EMP_FILE) EOF_FLG = ‘1’;

ON ERROR
BEGIN;
PUT SKIP EDIT (‘ 致命的エラー発生: 異常終了します ‘) (A);
SIGNAL FINISH;
END;

/ — ファイルオープン — /
OPEN FILE(EMP_FILE) INPUT;

PUT SKIP EDIT (‘=== 社員データ処理開始 ===’) (A);

/ — メインループ — /
READ FILE(EMP_FILE) INTO(EMP_RECORD);

DO WHILE (EOF_FLG = ‘0’);

  • 組み込み関数(BUILTIN)を活用した文字列トリムとデータ検証

IF VERIFY(TRIM(EMP_RECORD.EMP_ID), ’09’) = 0 THEN
DO;
/ 正常な数値IDの場合の処理ロジック /
PUT SKIP EDIT (
‘社員ID: ‘, EMP_RECORD.EMP_ID,
‘ / 氏名: ‘, EMP_RECORD.EMP_NAME,
‘ / 基本給:’, EMP_RECORD.BASE_SALARY
) (A, A, A, A, A, F(10));
END;
ELSE
DO;
PUT SKIP EDIT (‘警告: 不正な社員IDを検知 -> ‘, EMP_RECORD.EMP_ID) (A, A);
END;

/ 次レコード読み込み /
READ FILE(EMP_FILE) INTO(EMP_RECORD);
END;

/ — ファイルクローズ — /
CLOSE FILE(EMP_FILE);

PUT SKIP EDIT (‘=== 社員データ処理正常終了 ===’) (A);

END EMPLPR01;

—

4. コードの解説と現場で役立つデバッグのコツ

上のコードで、ベテランとして特に注目してほしいポイントをいくつか解説しておこう。

① `ALIGNED` 属性の明示的な指定

構造体の定義に `Dcl 1 EMP_RECORD ALIGNED` と書いている点に注目してくれ。
これを省略して `UNALIGNED` にすると、メモリのパディングが省かれて領域は節約できるが、数値フィールド(`FIXED BIN(31)`)が奇数バイト境界に配置される可能性がある。結果として、CPUがメモリアクセス時にペナルティ(ハードウェア例外や処理速度の低下)を受けることになる。基幹系バッチで大量のレコードを回す場合、この数バイトのパディングをケチるよりも、`ALIGNED` でCPU効率を最大化する方が圧倒的に正解なのだ。

② フィラー(`FILLER_1`)の重要性

VSAMや外部磁気テープ(GDG)とデータレイアウトをやり取りする際、COBOLのCOPY句に相当するレイアウトをPL/Iで再現することがよくある。COBOL側で `02 FILLER PIC X(2).` と定義されている部分を、PL/I側でうっかり忘れると、後続のフィールドのオフセットが丸ごとズレてしまい、データがめちゃくちゃになる。
`CHAR` 型の特性を理解し、レイアウト図通りに `FILLER` を正しく挟み込むことが、移行トラブルを防ぐ最大の防御壁となる。

③ 組み込み関数(BUILTIN)の積極的活用

コード内で使っている `TRIM` や `VERIFY` はPL/Iが誇る強力なBUILTIN関数だ。
特にメインフレームのデータは、パディングされた空白(スペース)が右側にゴロゴロ残っていることが多い。これをそのまま比較したり外部連携させると予期せぬバグの温床になる。文字列を扱うときは必ずこれらのビルトイン関数を適切に挟む癖をつけろ。

—

5. まとめ

PL/Iの `CHARACTER` 属性とアライメントの制御、少しは感覚を掴んでもらえただろうか。
「たかが文字、されど文字」。このメモリ配置の仕組みを理解しているか否かで、書くコードのパフォーマンスも、障害発生時の原因究明のスピードも劇的に変わってくる。

レガシーシステムの保守・モダナイゼーションの現場において、動くだけのコードは誰でも書ける。だが、ハードウェアの特性まで考慮された「美しいアライメントと堅牢なエラーハンドリングを持つコード」を書けるエンジニアこそが、今、現場で最も求められているんだ。

次のバッチ改修のときは、ぜひ今回の話を思い出して、構造体の定義を隅々まで見直してくれよ。期待しているぞ!

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