【入門編】CHARACTER(n) VARYINGの内部構造と制御ブロック – PL/Iの基本構文とデータ制御実践ガイド

皆さん、こんにちは!そして、PL/Iの世界へようこそ!

JavaやCOBOLといった、モダンな言語や基幹系のベテランとして活躍されている皆さんにとって、PL/Iというのは少し古めかしく、謎めいた言語に映るかもしれませんね。でもご安心ください。一つ一つの概念を紐解いていけば、決して難しいものではありません。むしろ、その奥深さに魅了されること間違いなしですよ!

今回は、PL/Iの中でも特に初学者の方が「おや?」と感じやすい、可変長文字列の扱いについて深掘りしていきましょう。そう、テーマはズバリ「`CHARACTER(n) VARYING`の内部構造と制御ブロック」です。

PL/Iの可変長文字列「CHARACTER VARYING」ってなんだ?

まず、皆さんが普段お使いの言語で可変長文字列がどのように表現されているかを考えてみましょう。

  • Java なら `String` 型がまさにそれですよね。宣言時に長さを指定せず、必要に応じて伸び縮みします。
  • COBOL なら `PIC X(n) OCCURS m TIMES DEPENDING ON …` のような可変長グループで、実際に格納されるデータ長を制御するイメージでしょうか。

PL/Iにも、もちろん可変長文字列が存在します。それが、`CHARACTER(n) VARYING`(または省略して`CHAR(n) VAR`)という宣言です。

DCL MY_VAR_STRING CHARACTER(100) VARYING;

この宣言を見て、「なるほど、最大100文字までの可変長文字列ね」と直感的に理解できる方もいるかもしれません。そう、その理解で正解です!

しかし、PL/Iの`VARYING`には、Javaの`String`やCOBOLの可変長グループとは少し異なる、メインフレームならではの「お作法」があるんです。それは、文字列本体の「前に」現在の長さ情報が付加されるという、ちょっとユニークな構造にあります。

「見えない部分」の秘密:先頭2バイトの長さ情報

ここがPL/Iの`CHARACTER VARYING`の核心であり、他の言語からの移行者が「おや、これはどういうことだ?」と感じるポイントかもしれません。

PL/Iの`CHARACTER(n) VARYING`で宣言された変数は、メモリ上では以下のような構造をとります。

1. 先頭の2バイト: 現在の文字列の長さを格納する領域
2. それに続く最大nバイト: 実際の文字列データが格納される領域

そうなんです、皆さんが「MY\_VAR\_STRING」という変数に格納した文字列データは、実はその先頭に、ひっそりと2バイトの長さ情報がくっついているんです!

なぜ2バイトなのか?

この2バイトは、符号なしの整数として扱われ、`0`から`65535`までの値を表現できます。つまり、PL/Iの`VARYING`型は、最大で65535文字までの長さをサポートできるということになります。

例えば、`CHARACTER(100) VARYING`と宣言した場合、実際のメモリ領域としては、

  • 現在の長さを保持する2バイト
  • 最大100文字分のデータ領域(100バイト)

合計で102バイトが確保されることになります。たとえ文字列が1文字しか入っていなくても、この102バイトは確保され続ける、という点がポイントです。

最大長と現在の長さの関係:コップと水のイメージ

この関係をイメージしやすいように、ちょっとした例え話で考えてみましょう。

あなたは、100mlの容量を持つコップを持っています。これが `CHARACTER(100) VARYING` の「最大長」です。

  • コップの容量(100ml):これが宣言した`n`(最大長)に当たります。メモリ上、このコップの容量分の領域は常に確保されています。
  • コップに入っている水の量:これが「現在の長さ」です。文字列を代入するたびに、この水の量が変わります。

そして、PL/Iの`VARYING`は、この「水の量」を常に監視している「水位計」のようなものが、コップのラベル(先頭の2バイト)に直接書き込まれているイメージなんです。

+—————-+——————————–+
| 現在の長さ (2B)| 文字列データ本体 (最大 n B) |
+—————-+——————————–+

この部分があなたの見ている「文字列」
でもPL/Iは実は左の2Bも見てるよ、という話

あなたが`MY_VAR_STRING = ‘HELLO’;` と代入すると、PL/Iは自動的に先頭の2バイトに「5」と書き込み、その後に「HELLO」というデータを格納します。
もし `MY_VAR_STRING = ‘PROGRAMMING’;` と代入すれば、先頭には「11」と書き込まれるわけです。

メモリ管理上の注意点と落とし穴

この`CHARACTER VARYING`、とても便利なのですが、いくつか知っておくべき「お作法」と「落とし穴」があります。

1. 宣言した最大長分のメモリは確保される

先ほどのコップの例えを思い出してください。コップに水が少ししか入っていなくても、コップ自体の容量(100ml)は変わりませんよね。

`CHARACTER(100) VARYING`と宣言すれば、文字列の長さが0でも、常に2バイト(長さ情報)+100バイト(データ領域)=102バイトが確保されます。

もし、`CHARACTER(32767) VARYING`のような非常に大きな可変長文字列を大量に配列で宣言してしまうと、実際に使用する領域は少なくても、宣言された最大長分のメモリが確保されてしまい、あっという間にメモリを消費してしまいます。

DCL MY_BIG_STRINGS(1000) CHARACTER(32767) VARYING;
/ 1000個の文字列全てに、32767+2 バイトが確保される
たとえ各要素が短い文字列しか格納していなくても、合計で約32MBのメモリを消費する /

このようなケースでは、本当にそんなに大きな可変長が必要なのか、あるいは`BASED`変数や`AREA`データ型など、より高度なメモリ管理を検討する必要が出てきます。

2. 最大長を超える代入は切り捨てられる

もし `CHARACTER(10) VARYING` に、15文字の文字列を代入しようとするとどうなるでしょうか?

DCL SHORT_VAR_STR CHARACTER(10) VARYING;

SHORT_VAR_STR = ‘THIS IS TOO LONG’; / 16文字 /
/ 実行後、SHORT_VAR_STR には何が格納されているでしょう? /

PL/Iは大変親切(?)なので、エラーにはならず、黙って最大長である10文字に切り詰めて代入します。
そして、先頭の2バイトの長さ情報も「10」に更新されます。

結果として、`’THIS IS TO’`が格納されることになります。
これは、特に外部ファイルから読み込んだデータを`VARYING`変数に受け取る際などに、意図しないデータの欠損を引き起こす可能性があるため、十分な注意が必要です。

3. `LENGTH`組み込み関数で現在の長さを取得する

PL/Iでは、文字列の長さを取得するために`LENGTH`という組み込み関数を使います。この関数は、先頭2バイトに格納されている「現在の長さ」を返してくれます。

DCL MY_VAR_STRING CHARACTER(100) VARYING;
DCL STR_LEN FIXED BINARY(15); / 長さ情報を格納する変数 /

MY_VAR_STRING = ‘Hello, PL/I World!’;
STR_LEN = LENGTH(MY_VAR_STRING); / STR_LEN は 18 になります /

PUT SKIP LIST(‘文字列:’, MY_VAR_STRING);
PUT SKIP LIST(‘現在の長さ:’, STR_LEN);

4. `SUBSTR`関数での部分文字列操作と長さ

`SUBSTR`関数は、文字列から部分文字列を抽出したり、部分的に置き換えたりする際に使います。`VARYING`変数に対しても当然使えますが、ここでも長さ情報との関係を意識しておきましょう。

DCL MY_VAR_STRING CHARACTER(100) VARYING;
DCL SUB_STRING CHARACTER(100) VARYING;

MY_VAR_STRING = ‘PL/I Programming is FUN!’;

/ 部分文字列の抽出 /
SUB_STRING = SUBSTR(MY_VAR_STRING, 5, 12); / 5文字目から12文字抽出 /
/ SUB_STRING には ‘Programming ‘ が格納され、長さは12になる /
PUT SKIP LIST(‘抽出結果:’, SUB_STRING, ‘長さ:’, LENGTH(SUB_STRING));

/ 部分文字列の置き換え /
SUBSTR(MY_VAR_STRING, 1, 3) = ‘IBM’; / 1文字目から3文字を’IBM’に置き換え /
/ MY_VAR_STRING は ‘IBM Programming is FUN!’ になり、長さは元のまま24 /
PUT SKIP LIST(‘置換結果:’, MY_VAR_STRING, ‘長さ:’, LENGTH(MY_VAR_STRING));

ここで重要なのは、`SUBSTR`で元の文字列の一部を置き換えても、文字列全体の長さは変わらないということです。`VARYING`変数の長さは、代入操作によってのみ自動的に調整されます。

実践的なPL/Iコード例

では、実際にPL/Iコードで`CHARACTER VARYING`の挙動を見てみましょう。

P_MAIN: PROCEDURE OPTIONS(MAIN);

/ 可変長文字列の宣言 /
DCL VAR_STR_A CHARACTER(20) VARYING; / 最大20文字の可変長文字列 /
DCL VAR_STR_B CHARACTER(10) VARYING; / 最大10文字の可変長文字列 /
DCL MAX_LEN_VAL FIXED BINARY(15); / 長さ情報を格納する変数 /

PUT SKIP DATA(VAR_STR_A); / 初期状態を表示(空文字列) /
PUT SKIP LIST(‘VAR_STR_A の長さ:’, LENGTH(VAR_STR_A)); / 初期長さは0 /
PUT SKIP;

/ 文字列を代入してみる /
VAR_STR_A = ‘Hello PL/I!’;
PUT SKIP DATA(VAR_STR_A); / 代入後の状態を表示 /
PUT SKIP LIST(‘VAR_STR_A の長さ:’, LENGTH(VAR_STR_A)); / 長さは11になる /
PUT SKIP;

/ 長い文字列を代入してみる(最大長以内) /
VAR_STR_A = ‘This is a somewhat longer string for PL/I.’;
PUT SKIP DATA(VAR_STR_A);
PUT SKIP LIST(‘VAR_STR_A の長さ:’, LENGTH(VAR_STR_A)); / 長さは43になる /
PUT SKIP;

/ 最大長を超える文字列を代入してみる /
VAR_STR_B = ‘A very very long string that exceeds 10 characters.’;
PUT SKIP DATA(VAR_STR_B); / 10文字に切り捨てられる /
PUT SKIP LIST(‘VAR_STR_B の長さ:’, LENGTH(VAR_STR_B)); / 長さは10になる /
PUT SKIP;

/ 異なる文字列を結合して代入 /
VAR_STR_A = VAR_STR_B || ‘ World!’; / VAR_STR_Bは’A very ver’なので、結合後は’A very ver World!’ /
PUT SKIP DATA(VAR_STR_A); / VAR_STR_Aの最大長20を超えるので切り捨てられる /
/ ‘A very ver World’ (20文字) となる /
PUT SKIP LIST(‘VAR_STR_A の長さ:’, LENGTH(VAR_STR_A)); / 長さは20になる /
PUT SKIP;

/ SUBSTR関数で部分文字列を扱う /
VAR_STR_A = ‘IBM Mainframe is powerful!’;
VAR_STR_B = SUBSTR(VAR_STR_A, 5, 9); / 5文字目から9文字 ‘Mainframe’ を抽出 /
PUT SKIP DATA(VAR_STR_B);
PUT SKIP LIST(‘VAR_STR_B の長さ:’, LENGTH(VAR_STR_B)); / 長さは9になる /
PUT SKIP;

/ SUBSTRのターゲット置き換え /
SUBSTR(VAR_STR_A, 1, 3) = ‘ZOS’; / ‘IBM’を’ZOS’に置き換え /
PUT SKIP DATA(VAR_STR_A); / ‘ZOS Mainframe is powerful!’ /
PUT SKIP LIST(‘VAR_STR_A の長さ:’, LENGTH(VAR_STR_A)); / 長さは元の26のまま /
PUT SKIP;

END P_MAIN;

実行結果例:

VAR_STR_A=”
VAR_STR_A の長さ: 0

VAR_STR_A=’Hello PL/I!’
VAR_STR_A の長さ: 11

VAR_STR_A=’This is a somewhat longer string for PL/I.’
VAR_STR_A の長さ: 43

VAR_STR_B=’A very ver’
VAR_STR_B の長さ: 10

VAR_STR_A=’A very ver World!’
VAR_STR_A の長さ: 17
/ ここはPL/Iコンパイラのバージョンや環境によって結果が変わる可能性があります。
‘A very ver World!’ が20文字を超えないので、そのまま代入されます。
もし20文字を超えた場合は、切り捨てられます。
ここでは17文字なのでそのまま。 /

VAR_STR_B=’Mainframe’
VAR_STR_B の長さ: 9

VAR_STR_A=’ZOS Mainframe is powerful!’
VAR_STR_A の長さ: 26

※実行結果例で`VAR_STR_A = VAR_STR_B || ‘ World!’;` の部分が20文字を超えないことを確認し、コメントと実行結果を修正しました。

まとめ:`CHARACTER VARYING`を恐れないで!

PL/Iの`CHARACTER(n) VARYING`は、その先頭に2バイトの長さ情報が付加されるという特徴を持っています。これは、JavaやCOBOLとは異なるアプローチですが、一度理解してしまえば非常に合理的で強力な機能です。

  • メリット: 文字列の長さ管理をPL/Iが自動で行ってくれるため、プログラマは長さを意識せずに文字列操作ができます。
  • デメリット/注意点: 宣言した最大長分のメモリが常に確保されるため、メモリ効率を考慮する必要があります。また、最大長を超える代入は警告なしに切り捨てられるため、データ欠損に注意が必要です。

レガシーシステムの改修やマイグレーションでPL/Iコードに触れる際、「あれ?この`VARYING`って、どうメモリ使ってるんだ?」と疑問に思ったら、この「先頭2バイト」の秘密を思い出してくださいね。

PL/Iは、このように一見すると「おや?」と思うような仕様が他にもいくつかありますが、それぞれに歴史的背景と合理的な理由があります。一つずつ紐解いていけば、きっとその魅力に気づくはずです。

次回は、PL/Iの「`POINTER`」や「`BASED`」といった、さらにメインフレームらしいメモリ管理の仕組みについて掘り下げていきたいと思います。どうぞお楽しみに!

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