【実務・中級編】BIT(n)のビット列操作とアライメント – PL/Iの基本構文とデータ制御実践ガイド

おい、お前ら、ちょっとこっちへ来い。今日はPL/Iの、それも「BIT(n)型のビット列操作とアライメント」について、腰を据えて話してやる。

PL/Iで基幹システムを組むなら、データ構造の設計はプログラムの根幹だ。特にビット単位でデータを扱う`BIT(n)`型は、メモリ効率を最大限に高めたり、外部システムとのインターフェースで厳密なデータレイアウトを要求されたりする場面で、必ずと言っていいほどお目にかかる。だがな、こいつは一歩間違えると、とんでもないバグの温床になりかねない。

マニュアルには書いてあるが、現場で本当に必要なのは、その裏にある挙動と、それをどう手なずけるかだ。今日は、俺が長年培ってきた知見を、コード例を交えながら余すところなく伝授してやろう。しっかり頭に入れておけよ。

1. BIT(n)型の基礎の基礎、だが落とし穴だらけだ

まず、`BIT(n)`型とは何か、ということからだ。これはな、指定したビット数(n)の論理値を保持するデータ型だ。`’1’B`や`’0’B`といったビット定数で表現される。

例えば、`DCL STATUS_FLAG BIT(1);`と宣言すれば、1ビットのフラグが定義される。これは一見するとシンプルだが、ここからが肝だ。

1.1. `ALIGNED`と`UNALIGNED`の魔術

PL/Iのデータ型には、メモリ上でどう配置されるかを制御する`ALIGNED`と`UNALIGNED`という属性がある。これ、意識して使ってるか?使ってないなら、いつか痛い目を見るぞ。

  • `ALIGNED` (整列): データは、そのデータ型が最も効率的にアクセスできるメモリアドレス(境界)に配置される。例えば、`FIXED BIN(31)`(4バイト)なら4バイト境界に、`FIXED BIN(63)`(8バイト)なら8バイト境界に配置されるのが一般的だ。CPUが一度に読み込めるデータ量に合わせることで、処理速度が向上する。
  • `UNALIGNED` (非整列): データは、直前のデータ型の直後に、隙間なく配置される。バイト境界に縛られない。メモリを節約したい時や、外部ファイルのような物理的なデータレイアウトに合わせたい時に使う。

ここで重要なのは、`BIT(n)`型は、デフォルトでは`UNALIGNED`になることが多い、ということだ。特に構造体(`STRUCTURE`)のメンバーとして宣言された場合、その挙動はさらに複雑になる。

//
/ PL/I プログラムの基本構造(パッケージとメインプロシージャ) /
//
PACKAGE MY_PLI_BATCH;

MY_MAIN_PROCEDURE: PROCEDURE OPTIONS(MAIN);

DCL 1 MY_HEADER, / ヘッダー情報 /
2 RECORD_TYPE CHAR(1), / レコード種別 /
2 TRANSACTION_ID FIXED BIN(31), / トランザクションID /
2 FLAGS, / 複合フラグフィールド /
3 PROCESS_FLAG BIT(1), / 処理フラグ (1ビット) /
3 ERROR_FLAG BIT(1), / エラーフラグ (1ビット) /
3 STATUS_CODE BIT(6), / ステータスコード (6ビット) /
2 FILLER CHAR(1); / 埋め草 /

DCL 1 MY_UNALIGNED_HEADER UNALIGNED, / 明示的にUNALIGNEDな構造体 /
2 RECORD_TYPE CHAR(1),
2 TRANSACTION_ID FIXED BIN(31),
2 FLAGS,
3 PROCESS_FLAG BIT(1),
3 ERROR_FLAG BIT(1),
3 STATUS_CODE BIT(6),
2 FILLER CHAR(1);

DCL 1 MY_ALIGNED_HEADER ALIGNED, / 明示的にALIGNEDな構造体 /
2 RECORD_TYPE CHAR(1),
2 TRANSACTION_ID FIXED BIN(31),
2 FLAGS,
3 PROCESS_FLAG BIT(1),
3 ERROR_FLAG BIT(1),
3 STATUS_CODE BIT(6),
2 FILLER CHAR(1);

DCL 1 MY_ALIGNED_HEADER_2 ALIGNED, / ALIGNED構造体内のBIT(n)の挙動確認 /
2 FIELD_A BIT(1),
2 FIELD_B BIT(1);

DCL 1 MY_UNALIGNED_HEADER_2 UNALIGNED, / UNALIGNED構造体内のBIT(n)の挙動確認 /
2 FIELD_A BIT(1),
2 FIELD_B BIT(1);

DCL (LENGTH, ADDR) BUILTIN; / BUILTIN関数を宣言 /

PUT SKIP LIST(‘— デフォルトのアライメント(環境依存) —‘);
PUT SKIP LIST(‘LENGTH(MY_HEADER) = ‘ || LENGTH(MY_HEADER));
PUT SKIP LIST(‘ADDR(MY_HEADER.RECORD_TYPE) = ‘ || ADDR(MY_HEADER.RECORD_TYPE));
PUT SKIP LIST(‘ADDR(MY_HEADER.TRANSACTION_ID) = ‘ || ADDR(MY_HEADER.TRANSACTION_ID));
PUT SKIP LIST(‘ADDR(MY_HEADER.FLAGS.PROCESS_FLAG) = ‘ || ADDR(MY_HEADER.FLAGS.PROCESS_FLAG)); / BIT(n)はアドレスが取れない /
PUT SKIP LIST(‘ (BIT(n)はバイト境界に位置しない可能性があり、ADDRは使えない)’);

PUT SKIP LIST(‘— UNALIGNED指定の構造体 —‘);
PUT SKIP LIST(‘LENGTH(MY_UNALIGNED_HEADER) = ‘ || LENGTH(MY_UNALIGNED_HEADER));
PUT SKIP LIST(‘ADDR(MY_UNALIGNED_HEADER.RECORD_TYPE) = ‘ || ADDR(MY_UNALIGNED_HEADER.RECORD_TYPE));
PUT SKIP LIST(‘ADDR(MY_UNALIGNED_HEADER.TRANSACTION_ID) = ‘ || ADDR(MY_UNALIGNED_HEADER.TRANSACTION_ID));
/ ここでもBIT(n)にはADDRは使えないが、構造体全体のサイズとオフセットは重要 /

PUT SKIP LIST(‘— ALIGNED指定の構造体 —‘);
PUT SKIP LIST(‘LENGTH(MY_ALIGNED_HEADER) = ‘ || LENGTH(MY_ALIGNED_HEADER));
PUT SKIP LIST(‘ADDR(MY_ALIGNED_HEADER.RECORD_TYPE) = ‘ || ADDR(MY_ALIGNED_HEADER.RECORD_TYPE));
PUT SKIP LIST(‘ADDR(MY_ALIGNED_HEADER.TRANSACTION_ID) = ‘ || ADDR(MY_ALIGNED_HEADER.TRANSACTION_ID));
/ ここでもBIT(n)にはADDRは使えない /

PUT SKIP LIST(‘— ALIGNED構造体内のBIT(n)のパディング —‘);
PUT SKIP LIST(‘LENGTH(MY_ALIGNED_HEADER_2) = ‘ || LENGTH(MY_ALIGNED_HEADER_2));
PUT SKIP LIST(‘— UNALIGNED構造体内のBIT(n)のパディング —‘);
PUT SKIP LIST(‘LENGTH(MY_UNALIGNED_HEADER_2) = ‘ || LENGTH(MY_UNALIGNED_HEADER_2));

END MY_MAIN_PROCEDURE;
END MY_PLI_BATCH;

上のコードを実行してみると、`LENGTH`関数の結果で構造体のサイズが異なることに気づくはずだ。特に`MY_ALIGNED_HEADER`と`MY_UNALIGNED_HEADER`を比較してみろ。`MY_ALIGNED_HEADER_2`と`MY_UNALIGNED_HEADER_2`の結果はもっと顕著だろう。

  • `CHAR(1)`は1バイト。
  • `FIXED BIN(31)`は4バイト。
  • `BIT(n)`はビット単位。

`ALIGNED`が指定されていると、`FIXED BIN(31)`が4バイト境界に配置されるよう、コンパイラが自動的にパディング(隙間)を挿入する。このパディングが、メモリダンプを読んだり、COBOLなどの他言語と連携したりする際に、「あれ?データがズレてるぞ!?」となる原因のトップランナーだ。

いいか、外部データや物理レコードのレイアウトと厳密に合わせたい場合は、必ず構造体全体に`UNALIGNED`属性を指定しろ。さもないと、コンパイラの親切心(アライメント調整)が仇となる。

1.2. BIT(n)はどこに格納されるのか?

`BIT(1)`は1ビットだが、実際のメモリはバイト(8ビット)単位で管理されている。じゃあ、`BIT(1)`が1つあったら、1バイト使うのか?それとも他のビットと詰め込まれるのか?

答えは「状況による」だ。

  • `DCL FLAG BIT(1);`のように単独で宣言された`BIT(n)`は、通常1バイトを消費する。残りの7ビットは未使用だ。これは処理効率を考慮してのこと。
  • しかし、`DCL 1 FLAGS UNALIGNED, 2 F1 BIT(1), 2 F2 BIT(1), …;` のように、`UNALIGNED`な構造体の中で連続して宣言された`BIT(n)`は、バイト境界をまたいで詰め込まれる可能性がある。例えば、`BIT(1), BIT(1), BIT(6)`は、合計8ビットで1バイトに収まる。

この挙動を理解せずに、外部ファイルから読み込んだデータを`BIT(n)`で受け取ろうとすると、「あれ、読み込んだはずのフラグが意図しない値になってるぞ?」という事態に陥る。それは、ファイル内の物理的なビット配置と、PL/Iプログラム内の`BIT(n)`の論理的な配置が食い違っているからだ。

2. レコード入出力とBIT(n)のリアル

基幹システムのPL/Iプログラムは、ほとんどがレコード入出力を伴う。ファイルからデータを読み込み、加工し、またファイルに書き出す。このプロセスで`BIT(n)`型が関わってくる場合、特に慎重な設計と実装が求められる。

2.1. COBOLとの連携におけるBIT(n)の注意点

メインフレームの世界では、PL/IとCOBOLが混在しているのが当たり前だ。COBOLの`PIC X`や`REDEFINES`句で定義されたデータレイアウトを、PL/Iで正確に再現する必要がある。

COBOLの`PIC X`はバイト単位でデータを扱う。例えば、COBOLで`01 WORK-REC. 05 STATUS-BYTE PIC X. 88 STATUS-A VALUE X’01’. 88 STATUS-B VALUE X’02’.` と定義された場合、これは1バイトのデータだ。PL/Iでこれを受け取るには、単純に`DCL STATUS_BYTE CHAR(1);`とすれば良いが、ビット単位でフラグが詰まっている場合は話が違う。

COBOLでは、`REDEFINES`を使って同じ領域をビット単位で再定義するような直接的な機能は限定的だ(`BIT`データタイプはPL/I特有)。そのため、COBOL側では`PIC X`で定義しておき、その`PIC X`の内容をPL/I側で`BIT(n)`として解釈することがよくある。

//
/ 外部ファイルレコード定義の例 /
/ (COBOLのPIC X(2)をPL/IのBIT(n)として解釈するケース) /
//
DCL 1 EXTERNAL_RECORD UNALIGNED, / 外部ファイルなのでUNALIGNED必須 /
2 KEY_FIELD CHAR(10), / キーフィールド /
2 CONTROL_FLAGS CHAR(2), / COBOLではPIC X(2)相当の制御フラグ /
2 DATA_FIELD CHAR(88); / データフィールド /

/ 上記のCONTROL_FLAGS (2バイト=16ビット) をPL/Iでビットフラグとして解釈 /
DCL 1 CONTROL_FLAGS_OVERLAY BASED(ADDR(EXTERNAL_RECORD.CONTROL_FLAGS)) UNALIGNED,
2 FLG_PROCESS_MODE BIT(2), / 処理モード (2ビット) /
2 FLG_UPDATE_PERMIT BIT(1), / 更新許可フラグ (1ビット) /
2 FLG_DELETE_PERMIT BIT(1), / 削除許可フラグ (1ビット) /
2 FLG_STATUS_A BIT(1), / ステータスA (1ビット) /
2 FLG_STATUS_B BIT(1), / ステータスB (1ビット) /
2 RESERVED_FLAGS BIT(10); / 予約領域 (10ビット) /
/ 合計 2+1+1+1+1+10 = 16ビット = 2バイト /

DCL SYSPRINT FILE RECORD OUTPUT ENV(F(133)); / 出力ファイル /
DCL SYSIN FILE RECORD INPUT ENV(F(100)); / 入力ファイル (仮定) /

ON ENDFILE(SYSIN) BEGIN;
PUT SKIP LIST(‘— ファイル終端 —‘);
GO TO EOJ;
END;

OPEN FILE(SYSPRINT) PAGESIZE(60);
OPEN FILE(SYSIN);

DO WHILE (‘1’B); / 無限ループ(ON ENDFILEでGO TO EOJ) /
READ FILE(SYSIN) INTO(EXTERNAL_RECORD);

PUT SKIP LIST(‘— レコード読み込み —‘);
PUT SKIP LIST(‘KEY_FIELD = ‘ || EXTERNAL_RECORD.KEY_FIELD);
PUT SKIP LIST(‘CONTROL_FLAGS = ‘ || EXTERNAL_RECORD.CONTROL_FLAGS);

/ BIT(n)として解釈したフラグ値の表示 /
PUT SKIP LIST(‘FLG_PROCESS_MODE = ‘ || FLG_PROCESS_MODE);
PUT SKIP LIST(‘FLG_UPDATE_PERMIT = ‘ || FLG_UPDATE_PERMIT);
PUT SKIP LIST(‘FLG_DELETE_PERMIT = ‘ || FLG_DELETE_PERMIT);
PUT SKIP LIST(‘FLG_STATUS_A = ‘ || FLG_STATUS_A);
PUT SKIP LIST(‘FLG_STATUS_B = ‘ || FLG_STATUS_B);
PUT SKIP LIST(‘RESERVED_FLAGS = ‘ || RESERVED_FLAGS);

/ フラグの更新例 /
IF FLG_PROCESS_MODE = ’01’B THEN DO; / 処理モードが特定の値の場合 /
FLG_UPDATE_PERMIT = ‘1’B; / 更新許可フラグを立てる /
PUT SKIP LIST(‘更新許可フラグを立てました。’);
END;

/ 更新後のCONTROL_FLAGSの内容を確認 (CHARとして) /
PUT SKIP LIST(‘更新後 CONTROL_FLAGS = ‘ || EXTERNAL_RECORD.CONTROL_FLAGS);

/ VSAMへの書き込みなどがあればここに記述 /
/ WRITE FILE(VSAM_FILE) FROM(EXTERNAL_RECORD); /
END;

EOJ:
CLOSE FILE(SYSPRINT);
CLOSE FILE(SYSIN);

この例では、`CONTROL_FLAGS CHAR(2)`で定義された2バイトの領域を、`BASED`変数を使って`CONTROL_FLAGS_OVERLAY`という構造体でビット単位にオーバーレイしている。この`BASED`変数にも`UNALIGNED`を忘れずにつけること。外部の物理レイアウトを正確に捉えるには、`UNALIGNED`が必須だ。

2.2. ONユニットとデータ整合性

もし外部ファイルから読み込んだデータが、プログラム内で想定している形式と異なっていたらどうなる?`BIT(n)`として解釈しようとした部分が、例えば数字以外の文字だったり、期待するビットパターンでなかったりした場合だ。

`BIT(n)`型は論理値なので、直接の`ON CONVERSION`は発生しにくい。しかし、`CHAR`型で読み込んだものを`BIT`型に変換しようとしたり、あるいは`BIT`型の値を算術演算に使おうとしたりすると、`ON CONVERSION`や`ON FIXEDOVERFLOW`が発生する可能性がある。

重要なのは、`BIT(n)`型でビットパターンを操作する際、それが有効なビットパターンであるかを常に意識することだ。特に外部からの入力は信頼できないものと考えるべき。

//
/ ONユニットとBIT(n)データのハンドリング例 /
//
DCL BINARY_DATA CHAR(1);
DCL BIT_FLAGS BIT(8);

ON CONVERSION BEGIN;
PUT SKIP LIST(‘— ON CONVERSION 発生 —‘);
PUT SKIP LIST(‘CONVERSION SOURCE = ‘ || ONCHAR || ONSOURCE);
/ エラーをログに記録したり、デフォルト値を設定したりする /
BIT_FLAGS = ‘00000000’B; / 例としてデフォルト値を設定 /
PUT SKIP LIST(‘BIT_FLAGSをデフォルト値に設定しました。’);
ONCODE = 0; / CONVERSIONエラーをリセットし、処理を継続 /
END;

BINARY_DATA = ‘A’; / 不正なバイナリデータ(ビット文字列ではない) /
PUT SKIP LIST(‘— 不正なデータをBIT(8)に代入 —‘);
BIT_FLAGS = BINARY_DATA; / ここでON CONVERSIONが発生する可能性 /
PUT SKIP LIST(‘BIT_FLAGS = ‘ || BIT_FLAGS);

BINARY_DATA = ‘0’; / 有効なデータ /
PUT SKIP LIST(‘— 有効なデータをBIT(8)に代入 —‘);
BIT_FLAGS = BINARY_DATA;
PUT SKIP LIST(‘BIT_FLAGS = ‘ || BIT_FLAGS);

この例では、`CHAR(1)`の`BINARY_DATA`を`BIT(8)`に代入しようとしている。もし`BINARY_DATA`が`’0’`や`’1’`といった文字で構成されていれば問題ないが、`’A’`のような文字だと`ON CONVERSION`が発生する。`ON CONVERSION`ユニット内で適切にエラーハンドリングを行うことで、プログラムの異常終了を防ぎ、堅牢性を高めることができる。

3. BIT(n)の論理演算とBUILTIN関数の活用

`BIT(n)`型の真骨頂は、その論理演算と、PL/Iが提供する強力なBUILTIN関数にある。これらを使いこなせば、複雑なビット操作もスマートに記述できる。

3.1. 論理演算子 (`&`, `|`, `¬`)

  • AND (`&`): 両方のビットが`1`のときだけ`1`。特定のビットが立っているかどうかのチェックや、特定のビットをクリアする(`0`にする)のに使う。
  • OR (`|`): どちらか一方でも`1`なら`1`。特定のビットをセットする(`1`にする)のに使う。
  • NOT (`¬`): ビットを反転させる(`1`を`0`に、`0`を`1`に)。

//
/ ビット論理演算の例 /
//
DCL CONTROL_BYTE BIT(8) INIT(‘01010101’B); / 制御バイト /
DCL MASK_BIT_4 BIT(8) INIT(‘00010000’B); / 4番目のビットを操作するマスク /
DCL MASK_BIT_1_2 BIT(8) INIT(‘11000000’B); / 1番目と2番目のビットを操作するマスク /

PUT SKIP LIST(‘初期値: CONTROL_BYTE = ‘ || CONTROL_BYTE);

/ — 特定のビットが立っているかチェック (AND演算) — /
IF (CONTROL_BYTE & MASK_BIT_4) = MASK_BIT_4 THEN DO;
PUT SKIP LIST(‘4番目のビットが立っています。’);
END;

/ — 特定のビットを立てる (OR演算) — /
CONTROL_BYTE = CONTROL_BYTE | MASK_BIT_1_2;
PUT SKIP LIST(‘1番目と2番目のビットを立てました: ‘ || CONTROL_BYTE);

/ — 特定のビットをクリアする (ANDとNOT演算) — /
CONTROL_BYTE = CONTROL_BYTE & ¬MASK_BIT_4;
PUT SKIP LIST(‘4番目のビットをクリアしました: ‘ || CONTROL_BYTE);

/ — 全ビット反転 (NOT演算) — /
CONTROL_BYTE = ¬CONTROL_BYTE;
PUT SKIP LIST(‘全ビット反転しました: ‘ || CONTROL_BYTE);

3.2. BUILTIN関数による高度なビット操作

PL/Iには、`BIT(n)`型を扱うための強力なBUILTIN関数がいくつもある。これらを使いこなせば、複雑なビット操作も簡潔に記述できる。

  • `SUBSTR(string, start, length)`: ビット文字列の一部を抽出する。文字文字列と同じ感覚で使える。
  • `LENGTH(string)`: ビット文字列の長さを取得する(ビット単位)。
  • `BIT(expression, length)`: 任意のデータ型をビット文字列に変換する。これは非常に強力で、数値や文字をビットとして解釈したい場合に威力を発揮する。
  • `BOOL(x, y, op)`: 2つのビット文字列に対して、指定された論理演算を行う。例えば、`BOOL(A, B, ‘1000’B)`は`A & B`と同じ意味になる。マスクを動的に生成するような場合に使うと便利だ。
  • `VERIFY(string, verify_string, position)`: 文字列(ビット文字列も可)が、指定された文字セット(ビットセット)に含まれない文字(ビット)を含んでいるかチェックする。

//
/ BUILTIN関数によるビット操作の例 /
//
DCL FLAGS_BYTE BIT(8) INIT(‘10110010’B); / 8ビットのフラグ /
DCL SOME_NUMBER FIXED BIN(15) INIT(255); / 符号なし整数 (2バイト) /
DCL CONVERTED_BITS BIT(16); / 変換後のビット列 /

PUT SKIP LIST(‘初期 FLAGS_BYTE = ‘ || FLAGS_BYTE);

/ — SUBSTRで特定のビットを抽出 — /
DCL IS_ACTIVE BIT(1);
IS_ACTIVE = SUBSTR(FLAGS_BYTE, 1, 1); / 最初のビットを抽出 /
PUT SKIP LIST(‘最初のビット (IS_ACTIVE) = ‘ || IS_ACTIVE);

DCL STATUS_SECTION BIT(3);
STATUS_SECTION = SUBSTR(FLAGS_BYTE, 3, 3); / 3ビット目から3ビット抽出 /
PUT SKIP LIST(‘ステータスセクション (3-5ビット目) = ‘ || STATUS_SECTION);

/ — BIT関数で数値からビット列へ変換 — /
CONVERTED_BITS = BIT(SOME_NUMBER, 16); / FIXED BIN(15)は16ビットで表現 /
PUT SKIP LIST(‘SOME_NUMBER (255) のビット表現 (16ビット) = ‘ || CONVERTED_BITS);

/ — BOOL関数で動的な論理演算 — /
DCL MASK_DYNAMIC BIT(8) INIT(‘00001111’B);
DCL RESULT_BOOL BIT(8);
RESULT_BOOL = BOOL(FLAGS_BYTE, MASK_DYNAMIC, ‘1000’B); / AND演算 (FLAGS_BYTE & MASK_DYNAMIC) /
PUT SKIP LIST(‘FLAGS_BYTE & MASK_DYNAMIC = ‘ || RESULT_BOOL);

/ — VERIFY関数でビットパターンを検証 — /
DCL INVALID_BIT_POS FIXED BIN(31);
INVALID_BIT_POS = VERIFY(FLAGS_BYTE, ’01’B); / ‘0’または’1’以外のビットがあるか? /
IF INVALID_BIT_POS = 0 THEN DO;
PUT SKIP LIST(‘FLAGS_BYTEは有効なビットパターンです。’);
END;
ELSE DO;
PUT SKIP LIST(‘FLAGS_BYTEに無効なビットパターンがあります。位置: ‘ || INVALID_BIT_POS);
END;

`BIT`関数は、例えば`PIC 9(n)`で定義されたCOBOLの数値データを、PL/Iで`CHAR(m)`として読み込み、その内容をビットパターンとして操作したい場合に非常に有用だ。数値がどのようにバイナリ表現されているかを知っていれば、これを使って特定のビットを抽出したり、フラグとして利用したりできる。

4. 実践的なコーディング例:VSAMレコードのビットフラグ操作

ここまでの知識を使って、VSAMレコード内のビットフラグを操作する具体例を見てみよう。VSAMレコードもまた、厳密なバイトレイアウトが要求される典型的な例だ。

//
/ VSAMレコード内のビットフラグ操作の例 /
//
PACKAGE VSAM_BIT_EXAMPLE;

VSAM_PROC: PROCEDURE OPTIONS(MAIN);

DCL VSAM_FILE FILE RECORD UPDATE ENV(VSAM RECSIZE(128)); / VSAMファイル宣言 /

/ VSAMレコードの構造体 – UNALIGNEDが重要 /
DCL 1 VSAM_RECORD UNALIGNED,
2 RECORD_KEY CHAR(8), / レコードキー /
2 CONTROL_BYTE_1 BIT(8), / 制御バイト1 (8ビット) /
2 STATUS_FLAGS_A, / ステータスフラグA (複合) /
3 FLAG_A1 BIT(1), / フラグA1 /
3 FLAG_A2 BIT(1), / フラグA2 /
3 FILLER_A BIT(6), / 埋め草A (合計8ビット = 1バイト) /
2 STATUS_FLAGS_B BIT(16), / ステータスフラグB (2バイト = 16ビット) /
2 DATA_AREA CHAR(100); / データ領域 /
/ 合計 8+1+1+2+100 = 112バイト。RECSIZEは128なので余裕あり /
/ (実際にはVSAM制御情報などで変わるが、ここではデータ部のみ) /

DCL (ADDR, LENGTH) BUILTIN;

/ テストデータ作成用の仮変数 /
DCL TEST_KEY CHAR(8) INIT(‘00000001’);

ON ENDFILE(VSAM_FILE) BEGIN;
PUT SKIP LIST(‘— VSAMファイル終端またはレコードなし —‘);
GO TO EOJ;
END;

ON KEY(VSAM_FILE) BEGIN;
PUT SKIP LIST(‘— キーエラー発生 —‘);
PUT SKIP LIST(‘ONCODE = ‘ || ONCODE);
PUT SKIP LIST(‘ONKEY = ‘ || ONKEY);
/ キーエラー時の適切なリカバリ処理またはエラーログ出力 /
SIGNAL ERROR; / エラーとして処理を中断するか、GO TOで継続するか /
END;

OPEN FILE(VSAM_FILE) DIRECT; / 直接アクセスモードで開く /

/ — レコードの読み込みとビットフラグの解析 — /
PUT SKIP LIST(‘— VSAMレコード読み込みと解析 —‘);
VSAM_RECORD.RECORD_KEY = TEST_KEY; / 検索キーを設定 /
READ FILE(VSAM_FILE) INTO(VSAM_RECORD) KEY(VSAM_RECORD.RECORD_KEY);

PUT SKIP LIST(‘読み込んだレコードキー: ‘ || VSAM_RECORD.RECORD_KEY);
PUT SKIP LIST(‘CONTROL_BYTE_1: ‘ || VSAM_RECORD.CONTROL_BYTE_1);
PUT SKIP LIST(‘FLAG_A1: ‘ || VSAM_RECORD.STATUS_FLAGS_A.FLAG_A1);
PUT SKIP LIST(‘FLAG_A2: ‘ || VSAM_RECORD.STATUS_FLAGS_A.FLAG_A2);
PUT SKIP LIST(‘STATUS_FLAGS_B: ‘ || VSAM_RECORD.STATUS_FLAGS_B);

/ — ビットフラグの更新 — /
PUT SKIP LIST(‘— ビットフラグの更新 —‘);
IF VSAM_RECORD.CONTROL_BYTE_1 = ‘00000000’B THEN DO;
VSAM_RECORD.CONTROL_BYTE_1 = ‘10000000’B; / 最初のビットを立てる /
PUT SKIP LIST(‘CONTROL_BYTE_1を更新: ‘ || VSAM_RECORD.CONTROL_BYTE_1);
END;

IF VSAM_RECORD.STATUS_FLAGS_A.FLAG_A1 = ‘0’B THEN DO;
VSAM_RECORD.STATUS_FLAGS_A.FLAG_A1 = ‘1’B; / FLAG_A1を立てる /
PUT SKIP LIST(‘FLAG_A1を更新: ‘ || VSAM_RECORD.STATUS_FLAGS_A.FLAG_A1);
END;

/ STATUS_FLAGS_Bの特定ビットを操作する例 /
/ 例えば、9番目のビットを立てる(16ビットのうち、左から数えて9番目) /
VSAM_RECORD.STATUS_FLAGS_B = VSAM_RECORD.STATUS_FLAGS_B | ‘0000000010000000’B;
PUT SKIP LIST(‘STATUS_FLAGS_Bの9番目のビットを立てる: ‘ || VSAM_RECORD.STATUS_FLAGS_B);

/ — レコードの書き戻し — /
REWRITE FILE(VSAM_FILE) FROM(VSAM_RECORD) KEY(VSAM_RECORD.RECORD_KEY);
PUT SKIP LIST(‘— VSAMレコードを書き戻しました —‘);

/ — 新規レコードの書き込み例 (KEYED OUTPUT) — /
PUT SKIP LIST(‘— 新規レコード書き込み例 —‘);
VSAM_RECORD.RECORD_KEY = ‘00000002’;
VSAM_RECORD.CONTROL_BYTE_1 = ‘01010101’B;
VSAM_RECORD.STATUS_FLAGS_A.FLAG_A1 = ‘0’B;
VSAM_RECORD.STATUS_FLAGS_A.FLAG_A2 = ‘1’B;
VSAM_RECORD.STATUS_FLAGS_A.FILLER_A = ‘000000’B;
VSAM_RECORD.STATUS_FLAGS_B = ‘1111000011110000’B;
VSAM_RECORD.DATA_AREA = ‘NEW RECORD DATA.’;
WRITE FILE(VSAM_FILE) FROM(VSAM_RECORD) KEYFROM(VSAM_RECORD.RECORD_KEY);
PUT SKIP LIST(‘新規レコード(キー: ‘ || VSAM_RECORD.RECORD_KEY || ‘)を書き込みました。’);

EOJ:
CLOSE FILE(VSAM_FILE);
PUT SKIP LIST(‘— 処理終了 —‘);

END VSAM_PROC;
END VSAM_BIT_EXAMPLE;

この例では、`VSAM_RECORD`という構造体を`UNALIGNED`で宣言している。これにより、`BIT(8)`や`BIT(1)`、`BIT(16)`といったビットフィールドが、外部のVSAMレコードの物理的なバイトレイアウトと正確に一致するように保証される。
`READ`と`REWRITE`、`WRITE`文でVSAMレコードの入出力を行い、その中で`BIT(n)`型のフィールドを直接操作している。`ON KEY`ユニットも忘れずに用意し、キーの重複や見つからないといったVSAM特有のエラーをハンドリングできるようにしておくことが重要だ。

5. デバッグの勘所とパフォーマンスへの影響

`BIT(n)`型は、その特性上、デバッグがやや厄介になることがある。特にアライメントの問題は、メモリダンプを読み解くスキルが不可欠だ。

5.1. メモリダンプの読み方

「データがズレてる!」と感じたら、迷わずメモリダンプを見ろ。`IPCS`などのツールを使って、プログラムの作業領域やファイルバッファのメモリダンプを取得し、期待するバイト列と、実際にPL/Iがデータを格納しているバイト列を比較するんだ。

  • `UNALIGNED`な構造体の場合、宣言通りのビット配置になっているか、1バイト単位で確認する。
  • `ALIGNED`な構造体の場合、PL/Iコンパイラが挿入したパディングがどこに入っているかを確認する。通常は`00`や`FF`などのダミーバイトで埋められることが多いが、環境やコンパイラオプションによって異なる場合もある。

`BIT(n)`変数は、デバッガで表示すると`’1010’B`のようにビット定数として表示してくれることが多い。しかし、それがメモリ上のどのバイトのどのビットに該当するのかは、`UNALIGNED`の知識がなければわからない。

5.2. パフォーマンスへの影響

  • `ALIGNED`: CPUはバイト境界やワード境界に整列されたデータを高速に読み書きできる。これがデフォルトなのはそのためだ。
  • `UNALIGNED`: 非整列データへのアクセスは、CPUにとって追加の処理が必要になることがある。例えば、目的のビットが複数のバイトにまたがっている場合、CPUは複数のバイトを読み込み、ビットシフトやマスク処理で目的のビットを抽出する必要がある。

これは通常、個々の操作ではごくわずかなオーバーヘッドだが、大規模なデータ処理で`UNALIGNED`なデータを大量にアクセスする場合、累積的なパフォーマンス低下につながる可能性はゼロではない。

とはいえ、現代のメインフレームは非常に高速だ。通常、`UNALIGNED`によるパフォーマンス低下は、I/O処理や他の複雑なロジックによるオーバーヘッドに比べれば小さいことが多い。
しかし、メモリ効率とデータレイアウトの正確性が最優先される場面(例えば、外部I/Fの厳密な定義や、大量のフラグを持つコンパクトなデータ構造)では、`UNALIGNED`を使うメリットの方がはるかに大きい。パフォーマンスがクリティカルな場合は、両方のパターンでテストを行い、ボトルネックがないか確認する慎重さも必要だ。

6. まとめ:BIT(n)型を使いこなせば、お前も一人前だ

どうだ?`BIT(n)`型は一見シンプルだが、その裏にはアライメントやパディングといった、メインフレームのハードウェアとコンパイラの挙動に関する深い知識が潜んでいる。

  • 外部データとの連携や厳密な物理レイアウトが必要な場合は、必ず`UNALIGNED`属性を使え。
  • 構造体内部の`BIT(n)`の配置は、上位の構造体のアライメント属性に影響されることを理解しろ。
  • `BASED`変数でのオーバーレイは、`UNALIGNED`との組み合わせで真価を発揮する。
  • `SUBSTR`や`BIT`、論理演算子を駆使して、スマートにビット操作を行え。
  • デバッグの際には、メモリダンプを読み解くスキルが必須だ。

これらの知識をしっかりと身につけていれば、お前はPL/Iの`BIT(n)`型を恐れることはない。むしろ、これを使いこなすことで、より効率的で堅牢な基幹システムを構築できる、真のメインフレームエンジニアになれるはずだ。

今日の話はここまでだ。しっかり復習して、現場で活かせよ。

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