PL/I動的文字列のサイズ、LENGTHとSTG、どっちを使う? – ベテランが教える現場での使い分け
おい、諸君!今日はPL/Iで可変長文字列を扱う上で、時々頭を悩ませる「LENGTH関数」と「STG関数」について、現場のリアルな視点から、どこよりも分かりやすく解説してやろう。単なる仕様の羅列じゃ面白くないだろう? 俺たちが普段、どんな「なぜ?」に直面して、どうやって解決してきたか、その辺の「味」も含めて話していくから、しっかりついてくるんだぞ。
1. なぜ動的文字列のサイズを知る必要があるのか? ~保守現場のリアルな声~
まずは、なんで俺たちが「文字列のサイズ」なんてものにこだわるのか、その根本から話そう。君たちも、古いPL/Iのバッチプログラムを触ったことがあるなら、こんな経験はないか?
- データ連携時の文字化けや切り捨て: 他システムから受け取った可変長文字列が、想定より長くてデータが欠けたり、逆に短すぎて無駄な領域ができてしまったり。
- VSAMレコードのサイズ最適化: データベースに書き込むレコードのサイズをどうにか小さくしたい。可変長フィールドの実際の長さを把握して、無駄を省きたいんだ。
- メモリ使用量の把握: 大規模なバッチ処理で、大量の可変長文字列を扱っていると、メモリをどれだけ食ってるか気になることがある。どこで無駄が出てるか知りたいんだ。
- ONユニットでのエラーハンドリング: 特にレコード入出力で `ON ERROR` や `ON OVERFLOW` が発生したとき、どんな長さのデータで問題が起きたのかを知ることで、原因究明がグッと楽になる。
これらの問題に直面したとき、頼りになるのがLENGTH関数とSTG関数だ。でも、この二つ、名前は似てるけど、返ってくる値は全然違う。だからこそ、正確に理解して使い分けることが、コードの品質と保守性を大きく左右するんだ。
2. LENGTH関数:今、どれだけ文字が入っているか?
LENGTH関数は、その名の通り、可変長文字列(VARYING属性)に現在格納されている文字(バイト)の数 を返してくれる、一番よく使う関数だ。
例えば、こんなコードを考えてみよう。
/ —————————————————————- /
/ LENGTH関数のデモ /
/ —————————————————————- /
DEMO_LENGTH: PROCEDURE OPTIONS(MAIN);
DECLARE STR_VARYING VARYING(255) INITIAL(‘Hello’); / 最大255バイトの可変長文字列 /
DECLARE CURRENT_LEN FIXED BINARY(31); / 現在の長さを格納する変数 /
/ 初期値の長さを取得 /
CURRENT_LEN = LENGTH(STR_VARYING);
PUT SKIP LIST(‘初期文字列: “‘ || STR_VARYING || ‘”‘);
PUT SKIP LIST(‘LENGTH(STR_VARYING) = ‘ || CURRENT_LEN); / この時点では 5 /
/ 文字列を更新 /
STR_VARYING = ‘Hello, PL/I World!’;
/ 更新後の長さを取得 /
CURRENT_LEN = LENGTH(STR_VARYING);
PUT SKIP LIST(‘更新後文字列: “‘ || STR_VARYING || ‘”‘);
PUT SKIP LIST(‘LENGTH(STR_VARYING) = ‘ || CURRENT_LEN); / この時点では 17 /
/ 空文字列の場合 /
STR_VARYING = ”;
CURRENT_LEN = LENGTH(STR_VARYING);
PUT SKIP LIST(‘空文字列: “‘ || STR_VARYING || ‘”‘);
PUT SKIP LIST(‘LENGTH(STR_VARYING) = ‘ || CURRENT_LEN); / この時点では 0 /
/ NULL値の場合 (VARYING文字列にはNULL値は直接代入できないが、
参照先がNULLの場合は長さ0として扱われることがある。
より正確には、代入前の状態や代入元で制御される。) /
/ 例: SUBSTRなどでNULL文字列を返された場合など /
/ ここでは便宜上、空文字列で例示 /
RETURN;
END DEMO_LENGTH;
このコードを見ると、LENGTH関数は「文字列として現在使われている領域のサイズ」を返していることが分かるだろう。`STR_VARYING` に `’Hello’` と入っていれば `5`、`’Hello, PL/I World!’` と入っていれば `17` を返す。`VARYING(255)` と宣言していても、実際に使われているのはその一部だ。
【現場の豆知識】
LENGTH関数は、固定長文字列(ặc属性)には使えない。固定長文字列は、常に宣言されたサイズそのものだからだ。もし固定長文字列に対してLENGTH関数を使おうとすると、コンパイルエラーになるか、あるいは意図しない結果になるので注意しろ。
3. STG関数:どれだけメモリを「確保」しているか?
一方、STG関数は少し毛色が違う。これは、可変長文字列変数(VARYING属性)に対して、コンパイラが実際に割り当てたストレージのサイズ を返してくれる。
「え? LENGTH関数と何が違うんだ?」と思うかもしれない。ここが肝心なところだ。
PL/IのVARYING文字列は、内部的に「現在の長さ」と「割り当てられた最大長」の2つの情報を持っている。LENGTH関数が返すのは「現在の長さ」。一方、STG関数が返すのは、その「割り当てられた最大長」に、内部的な管理情報(長さ格納用の2バイトなど)を加えたサイズなんだ。
/ —————————————————————- /
/ STG関数のデモ /
/ —————————————————————- /
DEMO_STG: PROCEDURE OPTIONS(MAIN);
DECLARE STR_VARYING_SHORT VARYING(50) INITIAL(‘Short’); / 最大50バイト /
DECLARE STR_VARYING_LONG VARYING(255) INITIAL(‘This is a much longer string.’); / 最大255バイト /
DECLARE STR_ALLOC_SIZE FIXED BINARY(31); / 割り当てサイズを格納 /
/ 短い文字列の割り当てサイズを取得 /
STR_ALLOC_SIZE = STG(STR_VARYING_SHORT);
PUT SKIP LIST(‘STR_VARYING_SHORT: “‘ || STR_VARYING_SHORT || ‘”‘);
PUT SKIP LIST(‘LENGTH(STR_VARYING_SHORT) = ‘ || LENGTH(STR_VARYING_SHORT)); / 5 /
PUT SKIP LIST(‘STG(STR_VARYING_SHORT) = ‘ || STR_ALLOC_SIZE); / 50 + 2 = 52 (理論値) /
/ 長い文字列の割り当てサイズを取得 /
STR_ALLOC_SIZE = STG(STR_VARYING_LONG);
PUT SKIP LIST(‘STR_VARYING_LONG: “‘ || STR_VARYING_LONG || ‘”‘);
PUT SKIP LIST(‘LENGTH(STR_VARYING_LONG) = ‘ || LENGTH(STR_VARYING_LONG)); / 29 /
PUT SKIP LIST(‘STG(STR_VARYING_LONG) = ‘ || STR_ALLOC_SIZE); / 255 + 2 = 257 (理論値) /
/ 最大長まで文字列を更新 /
STR_VARYING_SHORT = ‘This string is now longer than the initial short one, but still within the max 50 bytes.’;
STR_ALLOC_SIZE = STG(STR_VARYING_SHORT);
PUT SKIP LIST(‘更新後STR_VARYING_SHORT: “‘ || STR_VARYING_SHORT || ‘”‘);
PUT SKIP LIST(‘LENGTH(STR_VARYING_SHORT) = ‘ || LENGTH(STR_VARYING_SHORT)); / 50 /
PUT SKIP LIST(‘STG(STR_VARYING_SHORT) = ‘ || STR_ALLOC_SIZE); / 52 (変わらないはず) /
RETURN;
END DEMO_STG;
STG関数が返す値は、通常、宣言されたVARYINGの最大長に、PL/Iの内部管理用領域(通常2バイト)を加えた値になる。つまり、文字列の内容がどうであれ、その変数が「どれだけのスペースを確保しているか」 を示しているんだ。
【現場の豆知識】
STG関数は、VARYING属性の変数に対してしか意味がない。固定長文字列や、配列、構造体など、他のデータ型に適用しようとしても、コンパイルエラーになったり、意図しない結果になるから注意しろ。
4. LENGTH vs STG:現場でどっちを使うか? ~実践的な判断基準~
さて、ここが一番重要だ。君たちが現場で、いつ、どちらの関数を使うべきか。俺が経験則から教えよう。
4.1. LENGTH関数を使うべきケース
- データの内容そのものの長さを知りたいとき:
- VSAMレコードの可変長フィールドの実際の長さを確認したい。
- 外部システムとのデータ交換で、渡す文字列の実際の長さを計算したい。
- 画面出力や帳票出力で、表示される文字数を確認したい。
- ONユニットで、エラーが発生したレコードの「内容」の長さを特定したい。
要するに、「今、この変数に実際に入っている文字の数」 が知りたいときは、迷わずLENGTH関数だ。
4.2. STG関数を使うべきケース
- メモリ使用量や、変数に割り当てられた領域全体を知りたいとき:
- プログラム全体のメモリ使用量を概算したい(ただし、VARYING変数だけがメモリを消費するわけではない)。
- 特定のVARYING変数が、どれだけのメモリ空間を「確保」しているかを知りたい。
- レコード入出力のバッファサイズを調整する際、VARYING変数の最大長を考慮したい場合。例えば、VSAMのREAD/WRITE命令で、レコード長を計算する際に、VARYING変数の宣言サイズ(+2バイト)を考慮に入れる必要がある場合など。
STG関数は、「その変数が占有している、あるいは占有しうる最大領域」 を知りたいときに役立つ。ただし、通常はLENGTH関数の方が圧倒的に使用頻度が高いはずだ。
4.3. 典型的な間違いと注意点
- 「VARYING(255)」の変数を、LENGTH関数で取得した長さだけ書き込もうとして、レコード長を間違える。
- これはよくあるミスだ。LENGTH関数で取得した長さは、あくまで「内容」の長さ。VSAMレコードなどに書き込む際は、VARYING変数の定義サイズ(STG関数が示すものに近い)を考慮して、レコード全体の長さを計算する必要がある。
- STG関数で取得したサイズを、そのままデータの内容長だと思い込む。
- STG関数が返すのは、宣言された最大長 + 内部管理情報だ。内容の長さではない。
- 固定長文字列(ặc属性)にLENGTH関数やSTG関数を使おうとする。
- これはコンパイルエラーになるか、予期せぬ結果を招く。固定長文字列の長さは、宣言したサイズそのものだ。
5. 実践的なコード例 ~VSAMレコード入出力との連携~
ここでは、LENGTH関数とSTG関数が、実際のVSAMレコード入出力処理でどう使われるか、簡単な例を見てみよう。
/ —————————————————————- /
/ VSAMレコード入出力とLENGTH/STG関数の利用例 /
/ —————————————————————- /
VSAM_DEMO: PROCEDURE OPTIONS(MAIN);
/ VSAMファイル定義 /
DECLARE VSAM_FILE FILE RECORD SEQUENTIAL ENV(FBA); / 例: Fixed Block Architecture /
DECLARE RECORD_BUFFER CHAR(1000); / レコードバッファ (十分なサイズを確保) /
/ VSAMレコードの構造定義 /
DECLARE 1 VSAM_RECORD,
2 CUST_ID CHAR(10), / 固定長: 顧客ID /
2 CUST_NAME VARYING(50), / 可変長: 顧客名 /
2 CUST_ADDRESS VARYING(100), / 可変長: 住所 /
2 TRANSACTION_DT CHAR(8); / 固定長: 取引日 /
/ ワーク変数 /
DECLARE CURRENT_NAME_LEN FIXED BINARY(31);
DECLARE CURRENT_ADDR_LEN FIXED BINARY(31);
DECLARE RECORD_CURRENT_LEN FIXED BINARY(31);
/ — ファイルオープン — /
OPEN FILE(VSAM_FILE) OUTPUT; / 書き込みモードでオープン /
/ — データの準備 — /
CUST_ID = ‘1234567890’;
CUST_NAME = ‘株式会社テスト’;
CUST_ADDRESS = ‘東京都千代田区丸の内1-2-3’;
TRANSACTION_DT = ‘20231027’;
/ — レコードへの書き込み — /
/
- PL/Iでは、構造体変数を直接ファイルに書き込める。
- 可変長フィールドは、その時点での内容長 + 内部管理情報を含めて
- バッファに展開される。
/
RECORD_BUFFER = ”; / バッファをクリア /
PUT FILE(VSAM_FILE) FROM(VSAM_RECORD); / レコードを書き込み /
/
- 書き込み後のレコード長を知りたい場合:
- PL/Iでは、WRITE/PUT FROMで展開されたバッファの実際の長さを
- 直接取得する標準的な方法は限られる。
- ここでは、各可変長フィールドのLENGTH関数で内容長を取得し、
- 固定長フィールドと合計することで、レコードの内容長を推測する。
- 注意: これはあくまで「内容」の長さを推測するものであり、
- VSAMの実際のレコード長(ブロックサイズなど)とは異なる場合がある。
/
CURRENT_NAME_LEN = LENGTH(CUST_NAME);
CURRENT_ADDR_LEN = LENGTH(CUST_ADDRESS);
RECORD_CURRENT_LEN = LENGTH(CUST_ID) + CURRENT_NAME_LEN + CURRENT_ADDR_LEN + LENGTH(TRANSACTION_DT);
PUT SKIP LIST(‘— 書き込み完了 —‘);
PUT SKIP LIST(‘顧客名 (LENGTH): ‘ || CURRENT_NAME_LEN);
PUT SKIP LIST(‘住所 (LENGTH): ‘ || CURRENT_ADDR_LEN);
PUT SKIP LIST(‘レコード内容合計長 (推測): ‘ || RECORD_CURRENT_LEN);
/
- STG関数の例:
- CUST_NAME変数が確保しているストレージサイズを知りたい場合
/
PUT SKIP LIST(‘CUST_NAME 確保ストレージサイズ (STG): ‘ || STG(CUST_NAME)); / 50 + 2 = 52 (理論値) /
PUT SKIP LIST(‘CUST_ADDRESS 確保ストレージサイズ (STG): ‘ || STG(CUST_ADDRESS)); / 100 + 2 = 102 (理論値) /
/ — ファイルクローズ — /
CLOSE FILE(VSAM_FILE);
RETURN;
END VSAM_DEMO;
この例では、`VSAM_RECORD` という構造体の中に、固定長フィールドと可変長フィールドを混在させている。`PUT FILE(VSAM_FILE) FROM(VSAM_RECORD)` で書き込むと、PL/Iコンパイラが賢く、可変長フィールドの現在の長さを見て、適切なサイズでバッファに展開してくれる。
そして、書き込み後のレコードの「内容」の長さを知りたい場合は、LENGTH関数で各可変長フィールドの長さを取得し、固定長フィールドと合算して計算している。STG関数は、あくまでその変数が「確保している領域」を示していることを、改めて確認してほしい。
6. ONユニットとの連携 ~エラー発生時のデバッグに役立つ~
レコード入出力やVSAMアクセスで、`ON RECORD` や `ON ERROR`、`ON OVERFLOW` などのONユニットが活躍する場面は多い。そんな時、LENGTH関数は原因究明の強力な味方になる。
例えば、VSAMファイルへの書き込みで `ON OVERFLOW` が発生したとしよう。どのレコードでオーバーフローしたのか? そのレコードのどこが原因で長くなったのか?
/ —————————————————————- /
/ ONユニットとLENGTH関数の連携例 (概念) /
/ —————————————————————- /
ON_UNIT_DEMO: PROCEDURE OPTIONS(MAIN);
DECLARE VSAM_FILE FILE RECORD SEQUENTIAL ENV(FBA);
DECLARE RECORD_BUFFER CHAR(100); / 仮のバッファサイズ /
DECLARE 1 VSAM_RECORD,
2 FIELD1 CHAR(10),
2 FIELD2 VARYING(80); / 可変長フィールド /
/ — ON OVERFLOW ユニット — /
ON OVERFLOW BEGIN;
PUT SKIP LIST(‘ OVERFLOW ERROR ‘);
PUT SKIP LIST(‘発生時の FIELD1: “‘ || FIELD1 || ‘” (LENGTH: ‘ || LENGTH(FIELD1) || ‘)’);
PUT SKIP LIST(‘発生時の FIELD2: “‘ || FIELD2 || ‘” (LENGTH: ‘ || LENGTH(FIELD2) || ‘)’);
/ ここで、FIELD2のLENGTH関数で取得した長さが、
レコード長をオーバーフローさせた原因の特定に繋がる場合がある。 /
/ 実際には、ONCODEやSYNCCONTEXTなどの情報も併用してデバッグする /
SIGNAL ERROR; / エラーを再発生させるか、終了処理へ /
END;
/ — ファイルオープン — /
OPEN FILE(VSAM_FILE) OUTPUT;
/ — データ準備と書き込み (エラー発生をシミュレート) — /
FIELD1 = ‘HEADER’;
FIELD2 = ‘This is a very long string that will cause an overflow.’; / 80バイトを超える可能性 /
/
- このPUT処理で、レコード長がバッファサイズを超えると
- ON OVERFLOW ユニットが実行される。
/
PUT FILE(VSAM_FILE) FROM(VSAM_RECORD);
/ — ファイルクローズ — /
CLOSE FILE(VSAM_FILE);
RETURN;
END ON_UNIT_DEMO;
このように、ONユニット内でLENGTH関数を使って、問題のレコードの可変長フィールドの実際の長さを取得することで、「あと少しで収まったのに」「なぜこんなに長くなったんだ?」といった原因特定の手がかりが得られる。STG関数は、このケースではあまり直接的な役には立たないだろう。
7. まとめ:現場で活かすPL/Iの知恵
さて、ここまでLENGTH関数とSTG関数について、現場の視点から解説してきた。
- LENGTH関数: 「今、入っている文字数」。データの内容そのものの長さを知りたいときに使う。
- STG関数: 「確保されている領域サイズ」。変数全体のメモリ使用量や、バッファサイズを考えるときに参考にする。
PL/Iは、その歴史の長さゆえに、まだまだ現役で動いている基幹システムがたくさんある。今回触れたような、一見地味な関数の挙動を正確に理解することが、大規模バッチの改修や、将来的なマイグレーション作業において、君たちを助ける強力な武器になるはずだ。
コードを書くときは、常に「なぜそうなるのか?」を意識し、今回紹介したような現場の知恵を忘れずに、堅牢で保守しやすいプログラムを書いていってほしい。
何か分からないことがあれば、いつでも聞きに来い。俺が、このメインフレームの世界で培ってきた経験を、惜しみなく教えてやるからな!
