こんにちは、皆さん。日々のメインフレーム保守や、終わりの見えないオープン化マイグレーションの調査、本当にお疲れ様です。
これまで数々の巨大な勘定系や基幹システムの改修現場を見てきましたが、今でも時々、背筋が凍るようなバグに遭遇します。「画面からデータを更新したはずなのに、別の画面に遷移すると値が化けている」「夜間バッチで突如としてSOC4(ストレージ違反)やASRAが発生する」。その原因の多くを辿っていくと、決まって行き着くのがCICS通信エリア(`DFHCOMMAREA`)におけるデータ定義の不一致です。
今回は、PL/Iにおける固定小数点数(`FIXED BINARY` および `FIXED DECIMAL`)のメモリ上の表現と、CICS領域間通信でそれが引き起こす「静かなるメモリ破壊」について、現場のノウハウを交えて徹底的に解説します。
—
なぜ、COMMAREAの型不一致は「見えない爆弾」なのか?
CICSの世界では、プログラム間(あるいはオンライン画面とプログラム間)のデータ受渡しに `DFHCOMMAREA` を使います。ここで重要になるのは、CICSは通信エリアの中身を「単なるバイト列(文字データの集まり)」としてしか見ていないという点です。
送信側が「これは `FIXED BINARY` の 15桁(Fullword)だ」と思って送り出したバイナリデータを、受信側が「いや、これは `FIXED DECIMAL`(パック十進数)の 7桁だ」と勝手に解釈して受け取ったらどうなるでしょうか?
コンパイラは、私たちが書いた `DECLARE` 文の宣言通りにメモリアクセスコードを生成します。型が一致していなければ、指しているアドレスの解釈がズレ、最悪の場合は隣の領域のフラグやポインタを盛大に書き潰します。しかも、運が悪いことに、この不一致はコンパイルエラーにはなりません。本番稼働後の特定条件下でしか顕在化しない、極めてタチの悪いバグへと育つのです。
—
PL/Iにおける固定小数点数のメモリレイアウトを再確認する
まずは基本の復習です。PL/Iの固定小数点数には、大きく分けて以下の2つの流儀があります。
1. `FIXED BINARY` (固定長二進数)
- 内部的には2進数(整数)として保持されます。
- `PRECISION`(精度)の指定によって、占有するバイト数が変わります。
- 1 〜 15 ビット: 2バイト(`HALFWORD` / `FIXED BINARY(15)`)
- 16 〜 31 ビット: 4バイト(`FULLWORD` / `FIXED BINARY(31)`)
- 32 〜 63 ビット: 8バイト(`DOUBLEWORD` / `FIXED BINARY(63)`)
- コボルでいう `COMP` や `COMP-4` に相当します。演算スピードが最も速いため、ループカウンタや内部計算用に好まれます。
2. `FIXED DECIMAL` (固定長十進数 / パック十進数)
- 1バイトに2桁の数字を詰め込み、最後の4ビットに符号(C, D, Fなど)を置く「パック十進数(Packed Decimal)」として保持されます。
- 宣言した桁数(N)に対して `(N + 2) / 2` のバイト数を消費します(奇数桁・偶数桁の切り捨てに注意)。
- コボルでいう `PACKED-DECIMAL`(`COMP-3`)です。外部ファイル(VSAMなど)や、金額・数量といった「1円の狂いも許されない十進演算」の標準です。
これら二つを、お互いに何の変換も意識せず `DFHCOMMAREA` 上で直結させようとすると、致命的なズレが生じます。
—
実践:安全なCOMMAREA定義と型安全なコーディング
後輩の皆さんによく指導するのが、「CICSのCOMMAREA構造体は、送信側と受信側で全く同一のインクルード(コピーブック)を絶対に使用し、かつ型定義の魔術(暗黙の型変換やアライメントのズレ)を排除せよ」ということです。
以下のサンプルコードを見てください。オンラインのオーダー受付プログラムから、在庫更新プログラムへデータを渡す際の、模範的な `DFHCOMMAREA` の定義とハンドリングです。
1
/ ————————————————– /
/ プログラム名: ORDUPD01 (CICS在庫更新サブプログラム) /
/ テーマ: COMMAREAにおける安全なデータ定義と受渡し /
/ ————————————————– /
ORDUPD01: PROC(DFHCOMMAREA) OPTIONS(MAIN);
/ 外部から渡される通信エリアのインターフェース定義 /
DCL 1 DFHCOMMAREA BASED(COMMAREA_PTR),
/ 共通ヘッダー部:トランザクションID /
3 COM-TRAN-ID CHAR(4),
/ 数量: 演算用として FIXED BINARY(15) を採用 (2バイト) /
3 COM-ORDER-QTY FIXED BIN(15),
/ 金額: 精度と十進厳密性を重視し FIXED DECIMAL(9,2) を採用 (5バイト) /
3 COM-UNIT-PRICE FIXED DECIMAL(9,2),
/ 予備領域: 将来拡張用 /
3 COM-FILLER CHAR(20);
Dcl COMMAREA_PTR PTR;
/ ワーク変数 /
DCL WK-TOTAL-AMT FIXED DECIMAL(11,2) INIT(0);
/ CICS通信エリアのポインタアドレスをセット /
COMMAREA_PTR = ADDR(DFHCOMMAREA);
/ ———————————————— /
/ データ検証(ONユニットを活用した例外処理) /
/ ———————————————— /
ON CONVERSION BEGIN;
/ パック十進数のデータ異常や数値変換エラーをトラップ /
CALL SEND_ERROR_SCREEN(‘DATA_CONVERSION_ERROR’);
GOTO EXIT_ROUTINE;
END;
/ ———————————————— /
/ メイン処理ロジック /
/ ———————————————— /
/ 数量(BINARY)と単価(DECIMAL)の混血演算 /
/ PL/Iコンパイラが自動的に型昇格・変換を行うが、明示的なキャストを推奨 /
WK-TOTAL-AMT = DECIMAL(COM-ORDER-QTY, 7, 0) COM-UNIT-PRICE;
IF WK-TOTAL-AMT > 9999999.99 THEN
DO;
/ 金額オーバーフロー時の制御 /
CALL HANDLE_OVERFLOW();
END;
ELSE
DO;
CALL UPDATE_DB_INVENTORY(COM-ORDER-QTY, WK-TOTAL-AMT);
END;
EXIT_ROUTINE:
RETURN;
SEND_ERROR_SCREEN:
PROC(ERR-MSG);
DCL ERR-MSG CHAR();
/ CICS SEND MAP 処理等のエラー画面返却ロジック /
END SEND_ERROR_SCREEN;
HANDLE_OVERFLOW:
PROC;
/ オーバーフロー時のログ出力およびCOMMAREAへのエラーコード設定 /
COM-TRAN-ID = ‘ERR9’;
END HANDLE_OVERFLOW;
UPDATE_DB_INVENTORY:
PROC(P_QTY, P_AMT);
DCL P_QTY FIXED BIN(15);
DCL P_AMT FIXED DECIMAL(11,2);
/ DB2やVSAMへの更新処理(省略) /
END UPDATE_DB_INVENTORY;
END ORDUPD01;
このコードのポイントと「現場の知恵」
1. `BASED(COMMAREA_PTR)` の厳格な運用
CICSから渡される `DFHCOMMAREA` は、必ず `BASED` ストレージとして宣言し、ポインタ経由でマップします。これにより、リンケージセクションのオフセットズレを防ぎます。
2. `FIXED BIN(15)` と `FIXED DEC(9,2)` の混在における注意点
先ほど述べた通り、メモリ上での占有サイズは `FIXED BIN(15)` が 2バイト、`FIXED DEC(9,2)` が 5バイトです。もし送信側がここを `CHAR` や別の定義で誤って上書きすると、受信側で参照した瞬間に `ON CONVERSION` 割り込み(データ例外)が発生します。
3. `ON CONVERSION` ユニットによる防衛的プログラミング
メインフレームの現場では、「データは常に汚れている」という性悪説に立ったコーディングが必要です。COMMAREA経由で渡されたパック十進数(`FIXED DEC`)が不正なゾーン・数値ビットを持っている場合、演算時にハードウェア割り込み(System Abend S0C7など)が発生してCICSタスクがアブノーマルエンドします。これを事前に捕捉するために、必ず `ON CONVERSION` などの条件制御(ONユニット)を仕込んでおき、システム全体がダウンするのを防ぐのがプロの作法です。
—
移行・改修時のチェックリスト(先輩からの伝言)
もし皆さんが現在、古いCOBOL資産からPL/Iへのマイグレーション、あるいは既存PL/Iシステムの機能拡張を担当しているなら、以下のポイントを必ずコードレビューのチェックリストに加えてください。
- [ ] コピーブックの完全共有: 送信側と受信側で、構造体のメンバ順序、データ型、桁数(精度)が1バイトたりともズレていないか?(特にコンパイラのオプションによるアライメント調整 `BOUNDARY(FULL)` や `UNALIGNED` の違いに注意せよ)
- [ ] BINARYとDECIMALの安易なキャストの排除: パフォーマンスや記述の楽さだけで `FIXED BIN` と `FIXED DEC` を行ったり来たりさせていないか? データ境界でのパディング(パディングバイトの存在)により、意図したオフセットからズレていないかを確認するため、`PLIRETV` や `STORAGE` 組み込み関数(BUILTIN)を使って構造体のサイズ(`LENGTH`関数など)を必ず検証すること。
- [ ] 符号の取扱いの確認: 符号なし(`UNSIGNED`)を意図しているのか、デフォルトの符号付き(`SIGNED`)なのか。マイグレーション時にCOBOLの `COMP-3`(正の値のみ)をそのまま `FIXED DEC` に落とし込むと、符号ビットの解釈違いでマイナス値に見えるなどの笑えないトラブルが起きる。
—
おわりに
メインフレームのPL/Iは、ハードウェアの特性を限界まで引き出せる非常に強力で美しい言語です。しかし、その分「メモリの構造をプログラマが完全に支配している」という前提で動いています。
CICS通信エリアでのデータ定義の不一致は、システム全体の信頼性を揺るがす「見えない地雷」です。型定義の裏側にあるバイナリ表現の本質を理解し、一歩進んだ防衛的コーディングを心がけることで、後輩たちから「あの先輩、やっぱり頼りになるな」と言われるような、トラブル知らずの堅牢なシステムを一緒に作り上げていきましょう。
それでは、次回の技術コラムでお会いしましょう。
