PL/Iの「ループの深淵」:DO-WHILEとDO-UNTILが引き起こすレガシーの罠
メインフレームの現場で何十年と稼働し続けてきたバッチ処理。その心臓部であるPL/Iコードを眺めていると、現代の言語にはない、ある種の「武骨な自由度」に気づかされます。特に、新人やJava/C#出身のエンジニアが躓きやすいのが、`DO-WHILE`と`DO-UNTIL`の挙動、そしてPL/I特有の「予約語が存在しない」という設計思想がもたらす副作用です。
今回は、基幹システムのモダナイゼーションを担う諸君に向けて、このループ処理の裏側にある「レジスタとメモリ」の挙動を解剖していこう。
—
1. 判定タイミングが変える「アベンドの境界線」
PL/Iにおいて、`WHILE`は「前置判定」、`UNTIL`は「後置判定」です。この言葉の響き以上に、実務で恐ろしいのはループ変数(制御変数)がループ終了後にどの値を保持しているかという点です。
DO-WHILE(前置判定)
条件が偽であれば、ループ内の処理は一度も実行されません。
/i
/ 制御変数がループ開始条件を満たさない場合、即座にスキップ /
DCL I FIXED BIN(31) INIT(0);
DO WHILE (I > 0);
/ 0番目のレコード処理は行われない /
CALL PROCESS_RECORD(I);
I = I – 1;
END;
DO-UNTIL(後置判定)
必ず一度はループ内を通り、処理の終了後に条件を判定します。ここでJavaの`do-while`との決定的な違い、そして「動的メモリ操作」との相性が問題になります。
/i
/ 必ず1回は実行されるため、ポインタのNULLチェックを忘れると即ABEND /
DCL P PTR;
P = GET_FIRST_ADDR();
DO UNTIL (P = NULL());
/ 最初のレコードは処理されるが、ポインタがNULLの場合、
ここでデリファレンスが発生し、S0C4のアベンドを誘発する /
CALL PROCESS(P->DATA);
P = P->NEXT;
END;
実務の現場で、CICSオンライン処理中に「なぜかデータが1件余分に読み込まれる」あるいは「NULLポインタで落ちる」といった事象の多くは、この`UNTIL`の特性を理解せずに、前置判定の感覚でコードを書いてしまったことが原因です。
—
2. 予約語なき言語がもたらす最適化の影
PL/Iの恐ろしい(そして美しい)点は、`IF`や`DO`といったキーワードが予約語ではないことです。これらはコンテキストによって意味が決まります。これがコンパイラの最適化にどう影響するか。
コンパイラオプションで`OPTIMIZE(3)`を指定すると、ループ内の制御変数はレジスタ(GR: 汎用レジスタ)にキャッシュされます。もし、あなたがループ内で`BASED`変数を使ってメモリを直接操作し、誤って制御変数の領域を破壊(オーバーレイ)した場合、コンパイラはそれを予測できず、ダンプ解析で原因特定が極めて困難なバグを生みます。
特にパックデシマル(FIXED DEC)の符号ビット反転と組み合わさると地獄です。`COMP3`形式のデータがループ内で計算され、意図せず符号が反転した結果、ループ終了条件が「永遠に満たされない」無限ループに陥るケースを何度も見てきました。
—
3. マイグレーション時の設計指針
JavaやC#への移行を行う際、PL/Iの「後置判定」をどう書き換えるか。私は以下の戦略を強く推奨します。
1. ガード節の徹底: `DO-UNTIL`の内部でポインタやインデックスを参照する前に、必ずループ条件と同じチェックを冒頭に入れること。
2. インデックスの境界管理: PL/Iは配列の境界チェックをコンパイルオプション(`SUBSCRIPTRANGE`)で制御しますが、移行先の言語ではデフォルトで例外が発生します。移行元でアベンドしていた処理が、移行先では単に「データが化ける」というより危険な挙動に変わるリスクを考慮してください。
3. DB2埋め込みSQLとの共存: カーソルループにおいて、`WHILE`条件と`SQLCODE`のチェックを分離すること。特に`FETCH`後の`SQLCODE`判定は、ループの外側で制御変数としてフラグ管理する設計が、後々の保守性を劇的に高めます。
—
最後に:スペシャリストの視点
汎用機のコードは、単なる命令の羅列ではなく、メモリという広大な空間をいかに効率的に、かつ安全に駆け抜けるかという「芸術」です。
`DO-WHILE`と`DO-UNTIL`のどちらを使うか。これは単なる好みの問題ではありません。「異常系をどこで拾い上げるか」という設計思想そのものです。次にアベンドダンプ(SYSMDUMP)を読み解く際、ぜひこのループの判定タイミングと、その時のレジスタ値を重ね合わせてみてください。そこに、バグの正解が必ず眠っています。
技術の深淵を覗く諸君の検討を祈る。何か特定のモジュールで解析に詰まったら、いつでも議論しよう。
