【テクニカル・上級編】PL/Iの構造体アライメントとUNALIGNED属性の使い分け – PL/Iの基本構文とデータ制御実践ガイド

はじめに:なぜ現代のテックリードがPL/Iの構造体アライメントに悩まされるのか

レガシーシステムのモダナイゼーションや、Java/C#といったオープン系言語へのマイグレーション現場において、最もエンジニアを絶望の淵に追い込むもののひとつが、データ構造の「隠れたパディング(隙間)」である。

「なぜ、メインフレームから抽出したバイナリデータをオープン系でそのまま読み込ませると、数値が化けるのか?」
「なぜ、CICSの領域外参照(ASRAアベンド)が、特定のフィールド追加後に突然頻発したのか?」

これらの謎を解く鍵は、PL/Iが誇る(あるいは呪うべき)`ALIGNED` と `UNALIGNED` というデータ属性の制御にある。C言語の構造体アライメント感覚でPL/Iのコードを眺めていると、コンパイラが裏で勝手に行うメモリアライメントの最適化に足元をすくわれる。

本稿では、IBM Enterprise PL/Iのコンパイラ挙動、メモリレイアウトの物理的真実、そして動的ポインタ操作やDB2/CICSの境界領域で我々を待ち受けるエッジケースについて、システムアーキテクトの視点から徹底的に解剖する。

1. アライメントの基本原理:ALIGNED vs UNALIGNED

PL/Iにおける構造体(STRUCTURE)のデフォルトアライメントは、データ型の境界要件に依存する。特に指定しない場合、または `ALIGNED` を明示(あるいはデフォルトで適用)した場合、コンパイラはCPUのメモリアクセス効率を最優先し、各データ型を「そのサイズに応じた境界アドレス」に強制配置する。

対して `UNALIGNED` は、メモリ効率の最大化を優先し、データ間にパディング(空きバイト)を一切挟まず、連続してメモリを配置する属性である。

物理メモリ上のレイアウトの比較

次のような、文字、パックデシマル(COMP-3)、フルワード整数(FIXED BINARY(31))が混在する構造体を考えてみよう。

1
DCL 1 例文構造体 BASED,
2 枝番 CHAR(3),
2 金額 FIXED DEC(15,2),
2 コード FIXED BIN(31);

① `ALIGNED` 属性の場合(デフォルトまたは明示的)

IBMメインフレーム(System z)のアーキテクチャでは、4バイトの整数(FIXED BINARY(31))は4の倍数のメモリアドレスに配置される必要がある。奇数アドレスからロードしようとすると、ハードウェア例外(スカラー・オペランド・アベンドなど)を引き起こすか、マイクロコードによるエミュレーションで大幅な性能劣化を招く。
そのため、コンパイラは `CHAR(3)` の直後に1バイトのパディングを挿入し、`金額`(COMP-3は8バイト)を適切な境界に合わせ、さらに全体の構造体を調整する。結果として、メモリ消費量は意図したサイズよりも膨らむ。

② `UNALIGNED` 属性の場合

一方、構造体全体、あるいは個別のメンバに `UNALIGNED` を指定すると、パディングは完全に排除される。

1
DCL 1 例文構造体_U UNALIGNED,
2 枝番 CHAR(3),
2 金額 FIXED DEC(15,2),
2 コード FIXED BIN(31);

  • `枝番` (3バイト)
  • `金額` (8バイト:COMP-3)
  • `コード` (4バイト:FIXED BIN)

合計15バイトが、隙間なくギッシリと詰め込まれる。ネットワーク伝送用電文のパケットや、外部ファイル(QSAMなど)との入出力レコードレイアウトにおいて、物理サイズを厳密に一致させるためには、この `UNALIGNED` が不可欠となる。

2. 実践PL/Iコード:ベース変数とポインタによる動的メモリ操作の罠

基幹システムのオンライン(CICS)や大規模バッチでは、動的に取得したストレージ(`GET MAIN` や `ALLOCATE`)に対して、`BASED` 変数を重ね合せてデータ構造を解釈する手法が多用される。

ここでアライメントを意識し損ねると、深刻なバグを生む。以下のコードを見てほしい。

1
/ ——————————————————— /
/ 構造体アライメントとポインタ操作のサンプルプログラム /
/ ——————————————————— /
TEST_ALIGN: PROC OPTIONS(MAIN);

/ UNALIGNED属性を持つ外部電文フォーマット /
DCL 1 伝送電文 UNALIGNED BASED(P_BUF),
5 ヘッダ区分 CHAR(1),
5 シーケンス FIXED BIN(31,0),
5 明細データ CHAR(100);

DCL P_BUF POINTER;
DCL ワーク領域 CHAR(200) BASED;
DCL 取得サイズ FIXED BIN(31,0) INIT(105);

/ 動的にストレージを取得(CICSのGETMAINを想定) /
ALLOCATE ワーク領域 SET(P_BUF) LENGTH(取得サイズ);

/ 電文データをセット /
ヘッダ区分 = ‘A’;
シーケンス = 12345;
明細データ = ‘TEST-DATA-BODY’;

/ [アーキテクトの警告] /
/ UNALIGNED構造体内の FIXED BIN(31) (シーケンス) は、 /
/ 4の倍数アドレスにアライメントされていない可能性が高い。 /
/ この状態で直接演算を行うと、コンパイラは内部的に /
/ 複数バイトのフェッチ&シフト命令を生成し、処理が重くなる。 /

PUT SKIP LIST (‘シーケンス番号: ‘, シーケンス);

FREE ワーク領域;

END TEST_ALIGN;

アーキテクトの知見:非整列アクセスのペナルティ

System zのハードウェアは `UNALIGNED` なデータのロード・ストアを許容するが、それがキャッシュラインの境界を跨ぐ場合(Cross-cache-line reference)、CPUのパイプラインストールが発生し、数倍から数十倍のサイクルを消費する。数百万件を処理する夜間バッチにおいて、すべてのレコード構造体を漫然と `UNALIGNED` にしていると、CPU使用率(シックス秒)の跳ね上がりという形でシステムにしっぺ返しが来る。

3. パックデシマル(COMP-3)の内部符号反転バグとアライメント

PL/Iの `FIXED DEC(p,q)`(COMP-3)は、1バイトに2桁の数字をパックし、最下位ニブルに符号(C, D, Fなど)を格納する。

ここで `UNALIGNED` との組み合わせにおいて、外部システム(例えばJavaで動くWebフロントやLinux上のDB)との間でバイナリファイルをSFTP等で直接やり取りする際、以下のエッジケースが発生する。

1. コンパイラのパディング起因のオフセットズレ
オープン系言語側で構造体をパースする際、PL/I側がデフォルト(`ALIGNED`)でコンパイルされており、かつ構造体内に隠しパディングが存在していた場合、Java側のC言語ライブラリ(JNI等)や構造体マッピングのオフセット計算が完全に狂う。
2. 符号ニブルの破壊
動的ストレージのポインタキャスト時に、アライメント境界を誤って奇数バイトの位置を指定してCOMP-3データを強引に読み書きすると、ハードウェア例外ではなく「サイレントデータ破損(静かなるデータ破壊)」を引き起こし、最下位の符号ニブル(通常正なら `C`、負なら `D`、アンサインなら `F`)が期待しないビットパターンに書き換わる。

基幹システムのダンプ解析において、アベンドコード `S0C7`(Data Exception:不正なパックデシマル)に直面したとき、その原因の少なからずは、ポインタ操作におけるアライメントミスや、`ALIGNED`/`UNALIGNED` のミスマッチによる領域のズレに起因している。

4. 埋め込みSQL(DB2)とCICS環境におけるエッジケース対策

DB2ホスト変数における制約

PL/IでDB2のホスト変数を定義する際、構造体を使用することが多い。

1
DCL 1 顧客レコード,
5 顧客ID CHAR(8),
5 顧客名 CHAR(50),
5 登録日時 CHAR(26);

DB2のプリコンパイラ(SQL processor)は、生成されるC言語等のホスト構造体との整合性を保つため、アライメントに対して非常に厳格である。
もし `UNALIGNED` を明示的に指定した構造体をそのままホスト変数としてSQL文にバインドした場合、プリコンパイルエラーになるか、最悪の場合、DB2サブシステムとの間のデータ転送バッファでメモリオーバーレイ(領域破壊)を引き起こし、CICS領域全体を巻き込んで `DFHPC0002` や `AEYD` などの致命的なシステムアベンドを引き起こす。

対策:
DB2のホスト構造体や、CICSのコールド/サードパーティ・コピーブックを模した構造体については、原則としてコンパイラオプションやデフォルトの `ALIGNED` に従わせるか、通信領域(COMMAREA)のレイアウト設計において厳密なオフセット管理(REDEFINESやPOS類似の機能の活用)を行う必要がある。

5. モダナイゼーション(レガシー移行)へのインプリケーション

JavaやC#などのモダン言語へPL/Iシステムを移行する際、この「アライメントの差異」は避けて通れない最大の難所である。

  • Javaの場合: `ByteBuffer` や、Java 14以降のレコード(Record)、あるいはJNIを介したネイティブメモリ操作において、`ByteOrder` やパディングの詰め方をPL/Iの `ALIGNED`/`UNALIGNED` に完全に一致させなければならない。
  • C#の場合: `[StructLayout(LayoutKind.Sequential, Pack = 1)]` 属性を指定することで、PL/Iの `UNALIGNED` と同等の緊密なメモリレイアウトを再現できる。`Pack = 1` を忘れると、C#ランタイムが勝手に4バイトや8バイトのパディングを挿入し、レガシーファイルとの互換性が完全に失われる。

移行設計の現場では、現行メインフレームから出力されたダンプリストやクロスリファレンス(XREF)を読み解き、すべての構造体メンバのオフセット位置(Offset)を1バイト単位でマッピングシートに起こす作業が不可欠となる。ここを怠ると、移行テストの最終局面で「原因不明のデータ化け」という悪夢に悩まされることになる。

おわりに

PL/Iの構造体アライメントは、単なる「メモリの節約術」ではない。それはハードウェアのアーキテクチャとソフトウェアのデータ構造を極限までチューニングするための、先人たちの知恵と制約の結晶である。

`ALIGNED` と `UNALIGNED` の挙動を完全に掌握すること――それこそが、レガシーの深淵を覗くシステムアーキテクトに求められる、真の技術的素養なのである。

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