LENGTHとCURRENTSIZEの深層:メインフレームのメモリ管理とJava/C#移行における罠
ようこそ、メインフレームの深淵へ。
基幹システムの現場で、夜間バッチの突然のアベンド(ABEND)や、CICSオンラインでのストレージ違反(ASRA/ASRB)に頭を抱えた経験はないだろうか。
PL/Iという言語は、C言語のような低レイヤーのポインタ操作の自由度と、COBOLを凌駕する強力なデータ記述力を併せ持つ、実にスパルタンな言語だ。特にデータ制御の根幹をなす識別子とビルトイン関数の挙動を誤解していると、プロダクション環境で致命的な爆発を引き起こす。
今回は、PL/Iのデータ長取得において混同されがちな `LENGTH` と `CURRENTSIZE` の違いに焦点を当てる。単なるリファレンスの解説ではない。コンパイラ最適化、可変長文字列(VARYING)の裏側、ポインタによる動的メモリ操作、そしてJavaやC#へのマイグレーション(レガシー移行)における致命的な設計ミスを防ぐための知見を、余すところなく共有しよう。
—
1. 根本思想の違い:論理長(LENGTH)と物理占有サイズ(CURRENTSIZE)
まず、両者の本質的な違いを定義する。
- `LENGTH` ビルトイン関数:
文字列データの論理的な文字数(またはバイト数)を返す。特に `CHARACTER VARYING` 型に対して使用した場合、2バイトのプレフィックス(領域長を示す半ワード)を除いた、現時点で有効なデータの長さを返す。
- `CURRENTSIZE` ビルトイン関数:
データ構造体がメモリ上で実際に占有している物理的なバイト数を返す。ベース変数やロケータ(POINTER)を伴う動的領域、あるいは不規則な境界調整(アライメント)を考慮したサイズが必要な場面で真価を発揮する。
初心者は「長さを取得するなら `LENGTH` でいいだろう」と安易にコードを書きがちだが、これがCICSの通信領域(COMMAREA)の切り出しや、DB2のホスト変数構造体を扱う際に、恐ろしいサイレントバグを生む温床となる。
—
2. コードで見る挙動の違いとコンパイラの罠
百聞は一見に如かず。以下のPL/Iコードを見てほしい。実務でよく見られる、可変長文字列と構造体を組み合わせた定義だ。
1
/ =============================================================== /
/ LENGTH と CURRENTSIZE の挙動比較サンプルプログラム /
/ =============================================================== /
TEST_ENV: PROC OPTIONS(MAIN);
/ 可変長文字列を含む構造体の定義 /
DCL 1 W_RECORD based(P_REC),
3 W_HEADER_LEN FIXED BIN(15), / ヘッダ長 /
3 W_DATA_VAR CHAR(100) VARYING; / 可変長文字列 /
DCL P_REC POINTER;
DCL W_LEN_VAL FIXED BIN(31);
DCL W_SIZE_VAL FIXED BIN(31);
/ 動的メモリの確保(最大サイズを仮定) /
ALLOCATE W_RECORD;
/ 値の設定 /
W_DATA_VAR = ‘IBM MAINAFRAME MIGRATION’;
W_HEADER_LEN = LENGTH(W_DATA_VAR);
/ 1. LENGTH の評価 /
/ 戻り値は「データの論理的な文字数」となる /
W_LEN_VAL = LENGTH(W_DATA_VAR);
PUT SKIP EDIT (‘LENGTH(W_DATA_VAR) = ‘, W_LEN_VAL) (A, F(4));
/ 2. CURRENTSIZE の評価 /
/ 戻り値は構造体全体がメモリ上で占有する物理バイト数となる /
W_SIZE_VAL = CURRENTSIZE(W_RECORD);
PUT SKIP EDIT (‘CURRENTSIZE(W_RECORD) = ‘, W_SIZE_VAL) (A, F(4));
FREE W_RECORD;
END TEST_ENV;
このコードが示す恐るべき事実
`W_DATA_VAR` に格納された文字列の実際の文字数は24バイトだが、`W_RECORD` 全体を `ALLOCATE` した際の `CURRENTSIZE` は、`VARYING` の最大長(100バイト)+ プレフィックス長(2バイト)+ ヘッダ長(2バイト、ただし境界調整によるパディングが入ることもある)の物理サイズを返す。
もし、ストレージの転送(`CICS WRITEQ TS` や `EXEC SQL INSERT` など)において、`CURRENTSIZE` ではなく `LENGTH` の結果をバイト数として指定してしまったらどうなるか?
当然、データの後続にゴミ(前回領域にあった未初期化のメモリ上の残骸)が混入し、情報漏洩やデータ破損を引き起こす。逆に、物理サイズ分をそのまま送受信すべきところで論理長を使うと、後続データが切り捨てられる。この切り分けができていないアーキテクトは、レガシー移行の現場において即座に失格の烙印を押されることだろう。
—
3. ポインタ操作と不連続なストレージにおけるエッジケース
基幹システムのパフォーマンスチューニングの極みとして、ストレージプールからの手動領域管理(`ALLOCATE` / `FREE` や、アドレスの直接演算)を行うことがある。
ここで問題になるのが、ベース変数(Based Variable)に対する `CURRENTSIZE` の評価だ。
1
DCL 1 DYNAMIC_MAP BASED(P_MAP),
3 MAP_ID CHAR(4),
3 MAP_BODY CHAR(1) REFER(MAP_LEN),
3 MAP_LEN FIXED BIN(15);
PL/I特有の `REFER` オプション(動的長さを持つ構造体要素)を用いた場合、`CURRENTSIZE(DYNAMIC_MAP)` はコンパイル時の静的なサイズではなく、現在の `MAP_LEN` の値に基づいた動的な物理サイズを計算して返す。
ダンプ解析の現場から:S0C4アベンドの真相
ストレージ違反(System ABEND: S0C4)のシグタルを受け取り、IPCS(Interactive Problem Control System)でSDUMPを解析すると、ポインタ `P_MAP` が指す領域の境界をオーバーしてデータを書き込んでいるケースに遭遇する。
原因を辿ると、開発者が `CURRENTSIZE` で取得すべき動的サイズを、静的な `LENGTH` やハードコーディングされた定数で代用し、実際の `REFER` 領域よりも小さなメモリしか確保していなかった、という単純かつ致命的なミスに行き当たる。PL/Iコンパイラはポインタ先のメモリ範囲の逸脱を自動では検知してくれない。すべてはアーキテクトとプログラマの責任なのだ。
—
4. パックデシマル(COMP-3)と符号反転バグの罠
データ制御において避けて通れないのが、計算属性(`FIXED DECIMAL`、いわゆるパックデシマル)の扱いだ。
`CURRENTSIZE` は、パックデシマル変数の「メモリ上のバイト数」を正確に返す。例えば `FIXED DEC(7,2)` であれば、符号領域を含めて4バイト(`7桁 = 4バイト – 1 = 7桁表現`)を返す。
ここで、C#やJavaへの移行時に起きる最悪のバグについて警告しておこう。
メインフレームのパックデシマルは、最下位ニブルに符号(`C`、`D`、`F` など)を保持している。レガシーシステムの移行ツールが自動生成したJavaコードにおいて、この符号の存在を無視して単なる文字列や数値としてパースすると、負数が正数に反転したり、数値データが化ける「符号反転バグ」が多発する。
PL/I側で `CURRENTSIZE` を使って正確にバイト単位のバイナリを切り出し、移行先(Javaの `ByteBuffer` や C#の `BinaryReader`)へ正しくマッピングする設計を描けない限り、マイグレーションプロジェクトは確実に失敗する。
—
5. Java / C# へのモダナイゼーション(レガシー移行)における設計指針
もしあなたが、今まさにPL/I資産をJava(Spring Bootなど)やC#(.NET Core)へリライト、あるいはコンバージョンしようとしているテックリードなら、以下の原則を胸に刻んでほしい。
1. 「LENGTH」の概念の置き換え:
Javaの `String.length()` は「文字数(UTF-16のコードユニット数)」であり、メインフレームのバイト長やEBCDICの概念とは完全に乖離している。文字コード変換(IBM漢字コードからUTF-8への変換など)を挟む場合、論理長(`LENGTH`)ベースのループ処理は、マルチバイト文字の分断(文字化け)を引き起こす。バイト配列(`byte[]`)の長さを厳密に管理するレイヤーを必ず設けること。
2. 「CURRENTSIZE」の構造体パディング再現:
C#の `[StructLayout(LayoutKind.Explicit)]` や Javaのバイナリパーサライブラリを使用する際、PL/I構造体の境界調整(Alignment)ルール(`SYNCHRONIZE` オプションの有無など)を完全に再現する必要がある。PL/Iの `CURRENTSIZE` が返すサイズと、移行先言語での構造体サイズが1バイトたりともズレてはならない。通信電文の構造体定義書において、このサイズの一致確認こそが移行テストの最初の関門となる。
—
結びにかえて
PL/Iは古い言語ではない。極限までハードウェアの性能を引き出し、データの整合性を厳格に担保するための「思想」がコードの隅々にまで宿った、至高のシステム記述言語である。
`LENGTH` と `CURRENTSIZE`。たった二つのビルトイン関数であっても、その背後にあるメモリ管理の哲学を理解しているか否かで、あなたが構築するシステムの堅牢性は天と地ほどの差が出る。
レガシーマイグレーションの荒波の中であっても、メインフレームのアーキテクチャで培ったこの「物理と論理の分離」を見失わなければ、どのようなモダン環境であっても、揺るぎない堅牢なシステムを構築できるはずだ。
コードを書くときは、常にメモリの向こう側を想像せよ。それが、真のシステムアーキテクトの姿である。
