こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダンな、あるいは広く普及している言語の経験がある方にとって、汎用機の世界、特にPL/I(ピーエルワン)という言語は、少し古めかしく、かつ独特の厳しさを持った魔境のように見えるかもしれません。
特に「CICS(シーアイシーエス)」というオンライン制御プログラムのなかでデータをやり取りする通信エリア、`DFHCOMMAREA(コミットエリアではなくコエリアと読みます)` の話になると、「なんだか難しそう……メモリを壊したらどうしよう……」と、冷や汗が出てしまいますよね。
でも、安心してください。怖がる必要はまったくありません。
今回は、CICS通信の命綱である `DFHCOMMAREA` における、固定小数点数(FIXED BINARY / DECIMAL)の精度とデータ型の不一致が引き起こす「メモリ破壊の恐怖」について、一緒に優しく紐解いていきましょう。
—
1. なぜCICSの通信エリア(DFHCOMMAREA)はシビアなのか?
Javaで言えばメソッド間の引数渡し、Webアプリケーションで言えばAPIのJSONリクエスト・レスポンスに相当するのが、CICSの `DFHCOMMAREA` です。
プログラムAからプログラムBを呼び出すとき、IBMメインフレームは「このメモリ領域の先頭から〇バイト分を、そのまま次のプログラムに渡すね」という、非常に原始的かつダイレクトなメモリ共有を行います。
ここでJavaやC/C++経験者ならハッとするはずです。
そう、「送る側」と「受け取る側」でデータの型やサイズが1バイト、いや1ビットでもズレたら、その先の世界はどうなるか……?
受け取る側が「ここは整数型(4バイト)だと思って読んだら、送り手が文字データ(文字コード)の途中で切って送ってきた!」なんてことが起きた瞬間、メモリ上のデータはグチャグチャに解釈され、最悪の場合、CICSタスクそのものが異常終了(アベンド)したり、他の変数の領域まで書き換えてしまう「メモリ破壊」を引き起こすのです。
—
2. PL/Iの固定小数点数(FIXED)のクセを知ろう
PL/Iには、数値を扱うために主に2つのデータ型があります。
COBOLerの皆さんならお馴染みの「10進数」と「2進数」の世界です。
1. `FIXED DECIMAL`(パック10進数 / ゾーン10進数)
- COBOLの `COMP-3` や `PIC S9(n)` に非常に近いです。人間にとって直感的で、1桁ごとに10進数としてメモリに格納されます。
2. `FIXED BINARY`(2進整数)
- Javaの `int` や `short`、COBOLの `COMP` に近いです。コンピュータが最も得意とする2進数で数値を保持します。
ここでPL/I特有の「ちょっと意地悪な仕様」があります。それは宣言する「桁数」によって、メモリ上で占有するバイト数が自動的に変わるという点です。
FIXED BINARY(バイナリ)の罠
PL/Iで `FIXED BINARY(15)` と書いた場合、これは「2バイト(16ビット)の整数」として扱われます(符号付き)。では `FIXED BINARY(31)` と書くと? これは「4バイト(32ビット)」になります。
もし、送信側が `FIXED BINARY(31)`(4バイト)のつもりでデータを詰めたのに、受信側がうっかり `FIXED BINARY(15)`(2バイト)と定義していたらどうなるでしょう?
受信側は最初の2バイトだけを切り取って「よし、これが数値だ!」と読み込み、残りの2バイトを「次の別の変数」として読み始めてしまいます。
結果、その後のデータ構造(オフセット)がすべてズレていき、メモリ破壊の大惨事へと繋がるわけです。
—
3. 実コードで見る:通信エリアの定義と危険な不一致
百聞は一見にしかず。実際のPL/Iコードで、正しい定義と「やってはいけない不一致の例」を見てみましょう。
【正しい例】送信側と受信側で完全に一致している通信エリア
以下のコードは、CICSのリンケージセクション(呼び出された側が受け取るエリア)の典型的な定義です。
1
/ ————————————————せよ /
/ 受信側プログラムのDFHCOMMAREA定義(健全な状態) /
/ —————————————————- /
DCL 1 CICS_COMMAREA BASED(DFHPTR),
/ 顧客ID:文字データ 8バイト /
5 COM_CUST_ID CHAR(8),
/ 注文数量:2進整数(16ビット) = 2バイト /
/ ※Javaのshort、COBOLのPIC S9(4) COMPに相当 /
5 COM_QTY FIXED BIN(15),
/ 単価:10進数 7桁、小数2桁 = 4バイト(パック) /
/ ※COBOLのPIC S9(5)V99 COMP-3に相当 /
5 COM_PRICE FIXED DEC(7,2);
この定義であれば、`DFHCOMMAREA` 全体のサイズは 「8(CHAR) + 2(BIN) + 4(DEC) = 14バイト」 と綺麗に計算でき、送信側とこの構造が完全一致していれば安全にデータを受け渡せます。
—
【危険な例】型や精度の不一致によるメモリ破壊
では、もし送信側は `FIXED BIN(15)`(2バイト)で送ってきたのに、受信側(上記コード)のプログラマがうっかり以下のように書いてしまったらどうなるでしょうか?
1
/ —————————————————- /
/ 【危険!】受信側で勝手にサイズを変えてしまった場合 /
/ —————————————————- /
DCL 1 CICS_COMMAREA BASED(DFHPTR),
5 COM_CUST_ID CHAR(8),
/ 送信側はBIN(15)[2バイト]なのに、受信側でBIN(31)[4バイト]にしてしまった! /
5 COM_QTY FIXED BIN(31),
/ この瞬間、COM_PRICEの読み取り位置が「2バイト後ろ」にズレてしまう! /
5 COM_PRICE FIXED DEC(7,2);
何が起きるか?
`COM_QTY` が4バイトとして解釈されるため、本来 `COM_PRICE` が始まるはずのメモリのど真ん中までを `COM_QTY` が侵食してしまいます。
その結果、`COM_PRICE` の値は使い物にならないゴミデータになり、最悪の場合、この領域に値を書き戻した際にCICSの管理領域まで破壊し、トランザクションが異常終了(ABEND: ASRAなど)を引き起こします。
—
4. レガシー移行・改修時のトラブルシューティング指針
もしあなたが今、COBOLからPL/Iへの移行プロジェクトや、既存のPL/Iバグ改修に携わっていて、「CICS通信でおかしな数値が取れる」「原因不明のストレージ違反(OC4アベンドなど)が起きる」と悩んでいるなら、以下の手順でチェックしてみてください。
1. COPY句(インクルードメンバー)の共有を確認する
- PL/Iでは `%INCLUDE` 文を使って、送信側と受信側でまったく同じデータ定義(構造体)を共有するのが鉄則です。手抜きをして個別のプログラムにバラバラの定義を書かないようにしましょう。
2. バイナリのビット数(15や31)の指定を凝視する
- `FIXED BIN` とだけ書くと、コンパイラやオプションによってサイズが揺らぐことがあります。必ず `FIXED BIN(15)` や `FIXED BIN(31)` のようにビット数を明示してください。
3. アライメント(境界調整)の罠に注意する
- PL/Iではデフォルトで、4バイト境界や2バイト境界にデータを合わせようとする「アライメント」機能が働きます。通信エリアでこれをやられると、意図しないパディング(隙間バイト)が勝手に挿入され、COBOL側との通信でサイズがズレる原因になります。
- 対策: CICSの外部通信エリアやファイル定義では、構造体の先頭(または全体)に `UNALIGNED` 属性を付与し、余計な隙間を作らないように宣言するのが実務の定石です。
1
/ 実務の鉄則:余計な隙間を空けさせない UNALIGNED 指定 /
DCL 1 CICS_COMMAREA BASED(DFHPTR) UNALIGNED,
5 COM_CUST_ID CHAR(8),
5 COM_QTY FIXED BIN(15),
5 COM_PRICE FIXED DEC(7,2);
—
まとめ
いかがでしたでしょうか?
PL/Iの `FIXED BINARY` や `FIXED DECIMAL` は、メモリのレイアウトと直結しているため、最初は少し冷やっとするかもしれません。しかし、「何バイトの領域をどう解釈しているか」 というメモリの絵を頭の中でイメージできるようになると、怖さはすっと消えていきます。
「型を合わせる」「サイズを合わせる」「`UNALIGNED` でズレを防ぐ」。
この3つ星の原則を守りさえすれば、CICSの通信エリアはあなたにとって心強い味方になってくれます。
レガシーシステムの海原へ漕ぎ出すあなたの航海が、安全で実りあるものになりますように。それではまた、次の技術でお会いしましょう!
