【実務・中級編】LENGTH関数とSTG関数による動的文字列のサイズ取得 – PL/Iの基本構文とデータ制御実践ガイド

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は、その歴史の長さゆえに、まだまだ現役で動いている基幹システムがたくさんある。今回触れたような、一見地味な関数の挙動を正確に理解することが、大規模バッチの改修や、将来的なマイグレーション作業において、君たちを助ける強力な武器になるはずだ。

コードを書くときは、常に「なぜそうなるのか?」を意識し、今回紹介したような現場の知恵を忘れずに、堅牢で保守しやすいプログラムを書いていってほしい。

何か分からないことがあれば、いつでも聞きに来い。俺が、このメインフレームの世界で培ってきた経験を、惜しみなく教えてやるからな!

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