PL/Iの可変長文字列 `CHARACTER(n) VARYING` を極める!内部構造とトラブルシューティング
諸君、ご苦労様。メインフレームの第一線でシステムを支えている皆のことだ、PL/Iの文字列処理には日頃からお世話になっていることだろう。固定長文字列 `CHARACTER(n)` は直感的で扱いやすいが、実際の業務データは可変長であることがほとんどだ。そこで登場するのが、`CHARACTER(n) VARYING`、通称「VARYING型」だ。
このVARYING型、非常に便利で強力な機能だが、その内部構造とメモリ管理の仕組みをきちんと理解していないと、思わぬバグやパフォーマンス劣化、果てはシステムアベンドの原因になりかねない。今日は、このVARYING型の「裏側」に焦点を当て、熟練のエンジニアたる者として知っておくべき深い知識と、現場で役立つ実践的なテクニックを伝授しよう。
PL/I可変長文字列の基礎知識 `CHARACTER(n) VARYING`
まずは基本中の基本から確認していこう。VARYING型は、その名の通り「可変長」の文字列を扱うためのデータ型だ。
DCL WORK_STRING CHARACTER(100) VARYING;
この宣言の意味するところは、「最大100バイトの文字列を格納できる変数 `WORK_STRING`」ということになる。この `(100)` は「現在の長さ」ではなく、「格納可能な最大長」を示しているという点がポイントだ。
さて、ここからが本題だ。この `CHARACTER(100) VARYING` がメモリ上でどのように表現されるか、君は正確に説明できるだろうか?
実は、PL/IのVARYING型は、宣言された最大長 `n` に加えて、現在の文字列長を保持するための2バイトの領域が、データ本体の先頭に付加される形でメモリ上に確保される。
つまり、`CHARACTER(100) VARYING` と宣言された変数は、実際には `100 + 2 = 102バイト` のメモリを占有することになる。
制御ブロックの真実:先頭2バイトの役割
この先頭2バイトこそが、VARYING型の心臓部であり、「長さ情報」を格納する制御ブロックだ。PL/Iコンパイラとランタイムシステムは、この2バイトを常に監視し、文字列の代入や連結などの操作に応じて自動的に更新する。
- 格納情報: 現在の文字列長 (0~65535)
- データ形式: ハーフワードバイナリ (PIC S9(4) COMP-5 に相当)
なぜ2バイトなのかというと、VARYING型の最大長は `CHARACTER(32767) VARYING` や `CHARACTER(65535) VARYING` まで指定可能だからだ。2バイトのバイナリ値であれば、`0` から `65535` までの値を表現できる。
この長さ情報は、プログラマが意識しなくてもシステムが自動で面倒を見てくれる非常に便利な機能だ。しかし、この2バイトを直接操作しようとすることは、極めて危険な行為だと肝に銘じておいてくれ。 ポインタ演算などを駆使してこの制御ブロックを意図せず、あるいは意図的に不正な値で上書きしてしまった場合、システムは文字列の正しい長さを認識できなくなり、メモリ破壊やアベンドに直結する。
常に、文字列の長さを取得する際には `LENGTH` BUILTIN関数を、設定する際には代入文や他の文字列操作関数を使うように徹底してほしい。
メモリ管理とパフォーマンスへの影響
VARYING型は非常に便利だが、その裏側には常に最大長分のメモリが確保されているという事実がある。
DCL VARY_DATA CHARACTER(30000) VARYING;
たとえ `VARY_DATA` に格納されている文字列がたった10バイトであっても、この変数は `30000 + 2 = 30002バイト` のメモリを確保していることになる。これを大量に宣言したり、大きな配列として使用したりすると、不必要なメモリ消費につながり、ひいてはプログラムの性能に影響を及ぼす可能性がある。
特に、大規模なバッチ処理で一時的に大量のVARYING型データを扱う場合、この点を見落とすと、メモリ不足による`STORAGE`条件の発生や、ページイン・ページアウトの頻発による処理時間増大を招くことがあるので注意が必要だ。
レコード入出力 (RECFM=V, VSAM) との連携
ファイルI/OにおけるVARYING型の扱いは、さらに複雑になる。
- RECFM=V (可変長レコード) ファイル:
PL/Iの `STREAM` I/O (GET/PUT LIST/EDIT/DATA) は、内部でVARYING型変数の長さ情報を参照し、適切にデータの読み書きを行ってくれる。特に `RECFM=V` や `RECFM=VB` のファイルから読み込む場合、システムはレコードの先頭に付加されたレコード長情報と、VARYING型変数の長さ情報を自動で調整してくれるため、非常に透過的だ。
- RECFM=F (固定長レコード) ファイルでの可変長データ:
最も注意が必要なのが、このケースだ。`RECFM=F` のファイルに、論理的には可変長のデータを格納したい場合、プログラマ自身がそのデータの長さを管理する必要がある。例えば、データ本体の前に長さフィールドを別途設ける、といった設計が一般的だ。このような場合、VARYING型変数をそのまま使わず、固定長変数に`DEFINED`属性でオーバーレイするなどの工夫が必要になる。
- `RECORD` I/O と VSAM:
`RECORD` I/O (READ/WRITE FILE(ddname) INTO/FROM(variable);) では、PL/IはVARYING型変数そのものを1つの「レコード」として扱う。つまり、`CHARACTER(n) VARYING` 変数を `FROM` で書き出す場合、システムは変数先頭の2バイトの長さ情報も含めた `n+2` バイトを物理レコードとして書き出す。
同様に、`INTO` で読み込む場合も、ファイルから `n+2` バイトを読み込み、最初の2バイトを長さ情報として解釈する。このため、ファイルレコード長とVARYING変数の最大長が一致しない場合、`STRINGSIZE`条件や`ENDFILE`条件の誤検知、あるいはデータ破壊を引き起こす可能性がある。
VSAMでも同様の注意が必要だ。キーにVARYING型を使う場合、キー長は現在の文字列長ではなく、常に最大長が考慮されるか、あるいは特定のルールに従って処理される。このあたりの挙動は、使用するVSAMファイルの定義(KSDS, ESDS, RRDS)や、PL/Iコンパイラのバージョン、システム環境によって細かな差異があるため、必ず公式マニュアルを確認し、テストで検証することが重要だ。
実例で学ぶ!`VARYING`の活用と注意点
ここからは具体的なコード例を交えながら、VARYING型の使い方と、陥りやすい落とし穴、そしてその回避策を見ていこう。
コード例1: 基本的な文字列操作と長さの確認
最も基本的なVARYING型の使い方だ。`LENGTH` BUILTIN関数で現在の長さを取得し、文字列の代入によって長さ情報が自動更新されることを確認しよう。
SAMPLE: PROCEDURE OPTIONS(MAIN);
DCL WS_VARY_STRING CHARACTER(50) VARYING; / 最大50バイトの可変長文字列 /
DCL WS_FIX_STRING CHARACTER(10); / 固定長文字列 /
DCL WS_LEN FIXED BIN(15); / 長さ情報格納用 /
PUT SKIP LIST(‘— VARYING型 基本操作 —‘);
/ 初期状態は長さ0 /
WS_LEN = LENGTH(WS_VARY_STRING);
PUT SKIP LIST(‘初期状態:’, WS_VARY_STRING, ‘長さ:’, WS_LEN);
/ 文字列を代入すると長さ情報が自動更新される /
WS_VARY_STRING = ‘HELLO PL/I WORLD!’;
WS_LEN = LENGTH(WS_VARY_STRING);
PUT SKIP LIST(‘代入後:’, WS_VARY_STRING, ‘長さ:’, WS_LEN);
/ 長さの異なる文字列を代入 /
WS_VARY_STRING = ‘SHORT’;
WS_LEN = LENGTH(WS_VARY_STRING);
PUT SKIP LIST(‘短い文字列代入後:’, WS_VARY_STRING, ‘長さ:’, WS_LEN);
/ 長さ情報を超える文字列を代入しようとすると… /
/ ここではSTRINGSIZE条件は発生しない (デフォルトで切り捨て) /
WS_VARY_STRING = ‘THIS STRING IS TOO LONG FOR THE FIFTY CHARACTER VARIABLE!’;
WS_LEN = LENGTH(WS_VARY_STRING);
PUT SKIP LIST(‘長い文字列代入後(切り捨て):’, WS_VARY_STRING, ‘長さ:’, WS_LEN);
PUT SKIP LIST(‘切り捨てられた文字列の実際の長さ:’, LENGTH(‘THIS STRING IS TOO LONG FOR THE FIFTY CHARACTER VARIABLE!’));
/ 固定長文字列への代入 /
WS_FIX_STRING = WS_VARY_STRING; / VARYING型から固定長への代入、右詰めスペース埋め /
PUT SKIP LIST(‘固定長への代入:’, WS_FIX_STRING, ‘長さ:’, LENGTH(WS_FIX_STRING));
/ VARYING型への連結 /
WS_VARY_STRING = ‘PART1’ || ‘PART2’ || ‘PART3’;
WS_LEN = LENGTH(WS_VARY_STRING);
PUT SKIP LIST(‘連結後:’, WS_VARY_STRING, ‘長さ:’, WS_LEN);
END SAMPLE;
出力結果を見てわかるように、`LENGTH`関数は常に現在の文字列長を正確に返してくれる。また、VARYING型への代入は、その長さ情報が最大長を超えない限り、自動的に調整される。最大長を超えた場合は、デフォルトでは超過部分が切り捨てられ、`STRINGSIZE`条件が発生する可能性がある。これについては後述する。
コード例2: レコード入出力と`VARYING`
`RECORD` I/OでVARYING型を扱う際の、重要な設計パターンだ。特に固定長レコードファイルに可変長データを格納するようなケースで有効だ。
RECORD_IO_SAMPLE: PROCEDURE OPTIONS(MAIN);
/ 入力ファイル宣言 (固定長レコードを想定) /
DCL SYSPRINT FILE STREAM PRINT;
DCL INPUT_FILE FILE RECORD INPUT;
DCL OUTPUT_FILE FILE RECORD OUTPUT;
/ レコードフォーマット定義 /
/ データ部には可変長文字列を想定 /
DCL 1 FIX_RECORD_AREA, / 固定長レコード全体の構造 /
2 RECORD_TYPE CHAR(1), / レコード種別 /
2 KEY_FIELD CHAR(10), / キーフィールド /
2 DATA_LEN FIXED BIN(15), / データ部の長さ (プログラマが管理) /
2 DATA_BODY CHAR(100); / 可変長データ本体を格納する固定長領域 /
DCL 1 VARY_RECORD_AREA, / VARYING型をそのまま扱う構造 (FROM/INTO用) /
2 VARY_KEY_FIELD CHAR(10), / キーフィールド /
2 VARY_DATA_BODY CHAR(80) VARYING; / 可変長データ本体 /
DCL 1 OVERLAY_RECORD_AREA, / 固定長レコードをVARYING型でオーバーレイする構造 /
2 O_REC_TYPE CHAR(1),
2 O_KEY_FIELD CHAR(10),
2 O_DATA_LEN FIXED BIN(15),
2 O_VARY_DATA CHARACTER(100) VARYING / DATA_BODYにオーバーレイ /
DEFINED DATA_BODY; / ここがポイント! /
DCL WS_LOOP_COUNT FIXED BIN(31) INIT(0);
DCL EOF_FLAG BIT(1) INIT(‘0’B);
/ ON ENDFILE 条件ハンドラ /
ON ENDFILE(INPUT_FILE) BEGIN;
PUT SKIP LIST(‘入力ファイルの終わりに達しました。’);
EOF_FLAG = ‘1’B;
END;
/ INPUT_FILE と OUTPUT_FILE をオープン /
OPEN FILE(INPUT_FILE) DSN(‘YOUR.INPUT.DSN’) ;
OPEN FILE(OUTPUT_FILE) DSN(‘YOUR.OUTPUT.DSN’) ;
PUT SKIP LIST(‘— RECORD I/O と VARYING型 —‘);
/ データ作成 (OUTPUT_FILEへ書き出し) /
WS_LOOP_COUNT = 0;
DO I = 1 TO 3;
SELECT (I);
WHEN (1) FIX_RECORD_AREA.DATA_BODY = ‘SHORT STRING’;
WHEN (2) FIX_RECORD_AREA.DATA_BODY = ‘A BIT LONGER STRING FOR TESTING’;
WHEN (3) FIX_RECORD_AREA.DATA_BODY = ‘THIS IS A VERY LONG STRING THAT MIGHT APPROACH THE MAXIMUM LENGTH OF THE DATA BODY FIELD.’;
END;
FIX_RECORD_AREA.RECORD_TYPE = ‘A’;
FIX_RECORD_AREA.KEY_FIELD = ‘KEY’ || SUBSTR(‘0000000000′,1,2-LENGTH(STRIP(PUT(I,P’9′)))) || PUT(I,P’9’);
FIX_RECORD_AREA.DATA_LEN = LENGTH(FIX_RECORD_AREA.DATA_BODY); / プログラマが長さを設定 /
PUT SKIP LIST(‘書き出しレコード:’, FIX_RECORD_AREA.RECORD_TYPE,
FIX_RECORD_AREA.KEY_FIELD,
FIX_RECORD_AREA.DATA_LEN,
FIX_RECORD_AREA.DATA_BODY);
/ 固定長レコードとして書き出し /
WRITE FILE(OUTPUT_FILE) FROM(FIX_RECORD_AREA);
END;
CLOSE FILE(OUTPUT_FILE); / 一度閉じて、再度入力用としてオープン /
/ OUTPUT_FILE を入力用として再度オープン /
OPEN FILE(OUTPUT_FILE) DSN(‘YOUR.OUTPUT.DSN’) ; / DSNはINPUT_FILEと同じものを指定 /
/ レコード読み込みと処理 /
DO WHILE(EOF_FLAG = ‘0’B);
ON ERROR PUT SKIP LIST(‘エラー発生!’); / 一般的なエラーハンドリング /
/ 固定長レコードとして読み込み /
READ FILE(OUTPUT_FILE) INTO(FIX_RECORD_AREA);
IF EOF_FLAG = ‘1’B THEN LEAVE;
WS_LOOP_COUNT = WS_LOOP_COUNT + 1;
PUT SKIP LIST(‘— 読み込みレコード #’ || WS_LOOP_COUNT || ‘ —‘);
PUT SKIP LIST(‘RECORD_TYPE:’, FIX_RECORD_AREA.RECORD_TYPE);
PUT SKIP LIST(‘KEY_FIELD:’, FIX_RECORD_AREA.KEY_FIELD);
PUT SKIP LIST(‘DATA_LEN (プログラマ管理):’, FIX_RECORD_AREA.DATA_LEN);
/ VARYING型でオーバーレイされたフィールドを参照 /
/ O_VARY_DATAの長さ情報は、O_DATA_LENの値に基づいて調整される /
/ ここではDATA_BODY全体をオーバーレイしているので、DATA_BODYの内容がそのままO_VARY_DATAとなる /
/ もしO_VARY_DATAの長さをO_DATA_LENにしたい場合は、別途代入やSUBSTRで調整が必要 /
PUT SKIP LIST(‘DATA_BODY (固定長):’, FIX_RECORD_AREA.DATA_BODY);
PUT SKIP LIST(‘O_VARY_DATA (オーバーレイ):’, OVERLAY_RECORD_AREA.O_VARY_DATA);
PUT SKIP LIST(‘O_VARY_DATAの現在の長さ:’, LENGTH(OVERLAY_RECORD_AREA.O_VARY_DATA)); / DATA_BODYの実際の長さ /
/ 取得したDATA_LENを使って、DATA_BODYから必要な部分だけを取り出す /
PUT SKIP LIST(‘DATA_BODYからDATA_LEN分取得:’, SUBSTR(FIX_RECORD_AREA.DATA_BODY, 1, FIX_RECORD_AREA.DATA_LEN));
/ — 注意点: VARYING型変数を直接INTO/FROMする場合 — /
/ これは通常、RECFM=Vのファイルや、VARYING型変数の最大長とレコード長が一致する場合にのみ使うべき。 /
/ 固定長ファイルにVARYING型変数をINTOすると、レコード全体がVARYING変数の最大長+2として読まれる。/
/ 今回の例ではFIX_RECORD_AREAが113バイトだが、VARY_RECORD_AREAは92バイト。 /
/ 直接INTOすると、データが壊れる可能性が高いか、STRINGSIZE条件が発生する。 /
/ READ FILE(OUTPUT_FILE) INTO(VARY_RECORD_AREA); / / ←コメントアウトしてあるが、実行すると危険な例 /
END;
CLOSE FILE(INPUT_FILE);
CLOSE FILE(OUTPUT_FILE);
END RECORD_IO_SAMPLE;
この例では、固定長レコードの中に可変長データを格納するパターンを示している。`FIX_RECORD_AREA` の `DATA_LEN` フィールドでデータの長さをプログラマが管理し、`DATA_BODY` という固定長フィールドに可変長データを詰め込んでいる。
`OVERLAY_RECORD_AREA.O_VARY_DATA DEFINED DATA_BODY;` の行が重要だ。これにより、`DATA_BODY` のメモリ領域を `CHARACTER(100) VARYING` として参照できる。ただし、`O_VARY_DATA` の長さ情報はこの `DEFINED` 宣言だけでは自動的に `O_DATA_LEN` の値にはならない。あくまでも `DATA_BODY` の内容全体が `O_VARY_DATA` の最大長(`100`)として扱われる。可変長データとして利用する際は、`SUBSTR(O_VARY_DATA, 1, O_DATA_LEN)` のように`DATA_LEN`を使って切り出すのが安全だ。
もし、VARYING型変数を直接 `INTO`/`FROM` で使用する場合、ファイルレコード長とVARYING変数の最大長(+2バイト)が厳密に一致しているか、または `RECFM=V` ファイルでシステムが自動処理してくれる場合に限定すべきだ。異なる場合は、意図しないデータ破壊やアベンドに繋がりやすい。
コード例3: `VARYING`型とポインタ、`UNALIGNED`の危険性
これは絶対に真似してはいけないアンチパターンだ。VARYING型の先頭2バイトの長さ情報を、ポインタを使って直接書き換える危険性を示す。
DANGER_ZONE: PROCEDURE OPTIONS(MAIN);
DCL WS_VARY_STRING CHARACTER(50) VARYING; / 最大50バイトの可変長文字列 /
DCL P POINTER; / ポインタ変数 /
DCL 1 LEN_INFO_AREA BASED(P) UNALIGNED, / 長さ情報をオーバーレイするための構造体 /
2 CURRENT_LEN FIXED BIN(15) UNALIGNED, / 先頭2バイトに相当 /
2 DATA_PART CHARACTER(50) UNALIGNED; / データ本体に相当 /
PUT SKIP LIST(‘— VARYING型とポインタ (危険な操作) —‘);
WS_VARY_STRING = ‘ORIGINAL STRING’;
PUT SKIP LIST(‘初期状態:’, WS_VARY_STRING, ‘長さ:’, LENGTH(WS_VARY_STRING));
/ VARYING型変数の先頭アドレスをポインタPに設定 /
/ VARYING型変数は常にALIGNEDなので、UNALIGNED属性でBASEDするのは注意が必要 /
P = ADDR(WS_VARY_STRING);
/ ここからが危険な操作の始まりだ! /
/ LENGTH BUILTIN関数ではなく、制御ブロック(CURRENT_LEN)を直接参照・変更する /
PUT SKIP LIST(‘ポインタ経由の長さ情報:’, LEN_INFO_AREA.CURRENT_LEN);
PUT SKIP LIST(‘ポインタ経由のデータ本体:’, LEN_INFO_AREA.DATA_PART);
/ 長さ情報を不正な値に書き換える /
LEN_INFO_AREA.CURRENT_LEN = 1000; / 最大長の50を超えた不正な値を設定! /
/ WS_VARY_STRING を表示すると、不正な長さ情報でメモリを読み込もうとする /
/ これがシステムアベンドの原因になりうる! /
/ このPUT文で、ランタイムシステムはCURRENT_LENが指す不正な長さのメモリ領域を読もうとする /
/ もし偶然、その領域にアクセス可能なデータがあれば表示されるが、通常はガーベッジか、例外が発生する /
PUT SKIP LIST(‘不正な長さ情報設定後 (表示結果は保証されない):’, WS_VARY_STRING);
/ 再び正しい長さに戻してみる (それでも危険だが) /
LEN_INFO_AREA.CURRENT_LEN = LENGTH(‘RECOVERED’);
WS_VARY_STRING = ‘RECOVERED’; / この代入で長さ情報が再設定されるので、上記操作は実質無効化されるが、
不正な長さで表示しようとしたPUT文で既に危険な状態になっている /
PUT SKIP LIST(‘リカバリ後:’, WS_VARY_STRING, ‘長さ:’, LENGTH(WS_VARY_STRING));
/ DATA_PARTを直接変更する (これも推奨されないが、LENGTH情報は変更されない) /
SUBSTR(LEN_INFO_AREA.DATA_PART, 1, 5) = ‘xxxxx’;
PUT SKIP LIST(‘データ部分直接変更後:’, WS_VARY_STRING, ‘長さ:’, LENGTH(WS_VARY_STRING));
END DANGER_ZONE;
このコードは、`CURRENT_LEN` に不正な値をセットすることで、PL/Iランタイムシステムが`WS_VARY_STRING`を処理する際に誤ったメモリ領域を参照させようとする。このような操作は、通常はストレージ保護違反(`0C4`アベンドなど)を引き起こすか、メモリ上の他のデータを破壊してしまう。
`UNALIGNED`属性は、データが特定のアライメント境界に配置されることを強制しないため、メモリの効率的な利用には役立つが、CPUのアクセス効率が落ちる可能性がある。特に、`BASED`変数でVARYING型の制御ブロックをオーバーレイするような場合、アライメントとバイトオーダー(エンディアン)を深く理解していないと、意図しない値の読み書きに繋がるため、厳に慎むべきだ。
コード例4: ONユニットでの例外処理と`VARYING`
VARYING型に最大長を超える文字列を代入しようとした場合、`STRINGSIZE`条件が発生する。この条件を適切にハンドリングすることで、堅牢なプログラムを作成できる。
ON_UNIT_SAMPLE: PROCEDURE OPTIONS(MAIN);
DCL WS_SHORT_VARY CHARACTER(10) VARYING; / 最大10バイト /
DCL WS_LONG_STRING CHARACTER(30) INIT(‘THIS IS A LONG STRING’); / 21バイト /
PUT SKIP LIST(‘— ON UNIT と STRINGSIZE条件 —‘);
/ デフォルトのアクション (STRINGSIZE発生時に文字列が切り捨てられる) /
PUT SKIP LIST(‘STRINGSIZE条件発生前の長さ:’, LENGTH(WS_SHORT_VARY));
WS_SHORT_VARY = WS_LONG_STRING; / 10バイトに切り捨てられる /
PUT SKIP LIST(‘デフォルトアクション (切り捨て):’, WS_SHORT_VARY, ‘長さ:’, LENGTH(WS_SHORT_VARY));
/ ON STRINGSIZE SYSTEM; を設定 (デフォルトと同じ) /
ON STRINGSIZE SYSTEM;
WS_SHORT_VARY = ”; / 一旦クリア /
WS_SHORT_VARY = WS_LONG_STRING;
PUT SKIP LIST(‘ON STRINGSIZE SYSTEM (切り捨て):’, WS_SHORT_VARY, ‘長さ:’, LENGTH(WS_SHORT_VARY));
/ STRINGSIZE条件を捕捉し、独自の処理を行う /
ON STRINGSIZE BEGIN;
PUT SKIP LIST(‘
STRINGSIZE条件が発生しました! #’);
PUT SKIP LIST(‘ターゲット変数:’, ONCODE()); / ONCODEでエラーコード取得 /
PUT SKIP LIST(‘最大長:’, SIZE(WS_SHORT_VARY)); / VARYING型の最大長を取得 /
PUT SKIP LIST(‘ソース文字列長:’, LENGTH(WS_LONG_STRING));
/ ここでエラーログ出力、代替値設定、あるいは処理中止などの対応が可能 /
PUT SKIP LIST(‘警告: 文字列が切り捨てられました。’);
/ 今回は処理を継続させるため、何もしない (デフォルトの切り捨て動作が適用される) /
/ もしここで GOTO EXIT_PROGRAM; などとすれば、プログラムを異常終了させられる /
END;
WS_SHORT_VARY = ”; / 一旦クリア /
WS_SHORT_VARY = WS_LONG_STRING;
PUT SKIP LIST(‘カスタムON UNIT (切り捨て):’, WS_SHORT_VARY, ‘長さ:’, LENGTH(WS_SHORT_VARY));
/ ON STRINGSIZE SNAP; を設定すると、ダンプとログが出力される /
ON STRINGSIZE SNAP;
WS_SHORT_VARY = ”;
WS_SHORT_VARY = WS_LONG_STRING;
PUT SKIP LIST(‘ON STRINGSIZE SNAP (切り捨て):’, WS_SHORT_VARY, ‘長さ:’, LENGTH(WS_SHORT_VARY));
/ ON STRINGSIZE を無効化 /
ON STRINGSIZE SYSTEM; / または ON STRINGSIZE REVERT; /
/ BUILTIN関数の活用: SUBSTRを使って明示的に切り捨てる /
WS_SHORT_VARY = SUBSTR(WS_LONG_STRING, 1, LENGTH(WS_SHORT_VARY)); / VARYING型の最大長 /
PUT SKIP LIST(‘SUBSTRで明示的に切り捨て:’, WS_SHORT_VARY, ‘長さ:’, LENGTH(WS_SHORT_VARY));
END ON_UNIT_SAMPLE;
`ON STRINGSIZE` を適切に設定することで、意図しない文字列の切り捨てを防いだり、切り捨てが発生した際にログを出力したり、処理を中断したりといった柔軟な制御が可能になる。特に、外部からの入力データをVARYING型変数に格納する場合など、データの長さが予測不能な場面では、この`ON UNIT`によるハンドリングが不可欠だ。
`LENGTH(WS_SHORT_VARY)` は現在の長さを返すが、`SIZE(WS_SHORT_VARY)` はVARYING型の「宣言された最大長」を返す。この違いも覚えておこう。
デバッグのコツとトラブルシューティング
VARYING型が絡むトラブルは、その内部構造を知っていれば原因究明が格段に早くなる。
1. アベンド発生時:
`0C4`などのストレージ保護違反でアベンドした場合、原因はVARYING型の長さ情報が不正になり、意図しないアドレスにアクセスしようとしたことかもしれない。ダンプを解析する際は、問題となったVARYING型変数のアドレスを特定し、その先頭2バイトがどのような値になっているかを確認することだ。不正な長さ(例えば最大長を超えた値や負の値、非表示文字など)が入っていれば、それが原因だ。
2. `HEX` BUILTIN関数の活用:
プログラム実行中にVARYING型変数の内部状態を確認したい場合、`HEX` BUILTIN関数が非常に役立つ。
DCL WS_DEBUG_VARY CHARACTER(20) VARYING;
WS_DEBUG_VARY = ‘DEBUG TEST’;
PUT SKIP LIST(‘VARYING変数のHEXダンプ:’, HEX(WS_DEBUG_VARY));
この出力を見れば、先頭2バイトの長さ情報と、その後のデータ本体がどのように格納されているかを確認できる。
例えば、`’000A C4C5C2C8C740E3C5E2E3’` のような出力があった場合、
- `000A` (16進数) は10進数で10。これが現在の文字列長だ。
- `C4C5C2C8C740E3C5E2E3` がデータ本体 ‘DEBUG TEST’ のEBCDIC表現となる。
もしこの先頭2バイトが`0000` (長さ0) や、`0014` (長さ20) よりも大きな値になっているなど、現在の文字列と一致しない場合は、何らかの不正な操作が行われた可能性が高い。
3. `STRINGSIZE`条件の徹底したハンドリング:
ログに`STRINGSIZE`条件が発生したことを示すメッセージが頻繁に出る場合、データが意図せず切り捨てられていることを意味する。これは潜在的なデータ破損だ。`ON STRINGSIZE`ユニットを設定し、発生箇所を特定して原因を究明し、適切なデータ処理を行うようプログラムを修正すべきだ。
まとめ
`CHARACTER(n) VARYING` は、PL/Iが提供する非常に強力で便利なデータ型だ。しかし、その内部で常に先頭2バイトの長さ情報という制御ブロックを保持していることを理解し、これをシステムが自動で管理してくれるという原則を忘れてはならない。
- 安易なポインタ操作や長さ情報の直接書き換えは、極めて危険な行為であり、厳禁だ。
- VARYING型変数を宣言すると、常に最大長+2バイトのメモリが確保されることを意識し、メモリ使用量に配慮すること。
- `RECORD` I/OやVSAMアクセスでは、VARYING型変数が持つ長さ情報がどのように扱われるかを正確に理解し、ファイル定義やレコード構造との整合性を保つこと。
- `LENGTH` BUILTIN関数を積極的に活用し、`STRINGSIZE`条件を適切にハンドリングすることで、堅牢で信頼性の高いプログラムを構築できる。
諸君、PL/Iは奥が深い。特にメインフレームの基幹システムでは、メモリやI/Oといった物理的な制約を常に意識した設計・コーディングが求められる。このVARYING型の内部構造をマスターすることは、安定したシステム運用と、予期せぬトラブルを未然に防ぐための重要な一歩となるだろう。
今日の話をしっかり頭に入れて、これからも現場を頼もしく支えていってほしい。期待しているぞ!
