1. 導入:配列処理の落とし穴にご用心!
COBOLで配列(テーブル)を扱う際、USAGE IS INDEX を使って添字(インデックス)を定義するのは基本中の基本ですよね。高速な処理が期待できるため、多くの現場で活用されています。しかし、この便利な USAGE IS INDEX 項目には、知らずにいると思わぬバグやコンパイルエラーに繋がる「比較制限」が存在します。
「なぜか数値項目と比較できない!」「コンパイルが通らない!」といった経験はありませんか?この豆知識では、その原因と具体的な解決策をしっかり解説します。効率的なCOBOLプログラミングと、将来のバグ回避のために、ぜひ最後までお読みください。
2. 基礎知識:USAGE IS INDEX とは?
まずは、USAGE IS INDEX についておさらいしましょう。
- USAGE IS INDEX:
配列(テーブル)の添字として使用されるデータ項目で、その名の通り「インデックス」を保持します。通常の数値項目(PIC 9)とは異なり、内部的にはメモリー上の物理的なアドレスオフセットや、そのテーブルの「通し番号」のような形で表現されます。これにより、配列要素へのアクセスが非常に高速に行われます。 - INDEXED BY:
テーブル定義(OCCURS句)の際に、そのテーブルの添字として使う名前を指定する句です。この INDEXED BY で定義された名前も、内部的には USAGE IS INDEX 項目として扱われます。
例:05 WS-ITEM OCCURS 10 TIMES INDEXED BY WS-IDX.
ここが重要!USAGE IS INDEX の比較制限
USAGE IS INDEX 項目の一番の特性、そして注意すべき点、それは「比較」に関する制限です。
USAGE IS INDEX 項目は、基本的に以下の項目としか直接比較できません。
- 別の USAGE IS INDEX 項目
- そのテーブルに対応する INDEXED BY で定義された添字項目
つまり、通常の数値項目(PIC 9 など)とは直接比較できないのです!
「IF WS-IDX = 5 THEN…」のような記述は、コンパイルエラーになるか、予期せぬ結果を引き起こす可能性があります。
3. 実装/解決策:数値との比較を可能にする「SET」命令
では、USAGE IS INDEX 項目と通常の数値項目を比較したい場合はどうすれば良いのでしょうか?
答えは、COBOLのSET命令を使うことです。SET命令は、USAGE IS INDEX 項目と数値項目の間で値を相互に変換する役割を担います。
具体的には、以下の2つの方法が考えられます。
解決策1:USAGE IS INDEX 項目を数値項目へ変換して比較する
これが最も一般的で分かりやすい方法です。
SET 数値項目 TO USAGE-IS-INDEX項目.
IF 数値項目 = 比較したい数値 THEN ...
SET命令を使うことで、内部的にUSAGE IS INDEX項目の値(物理アドレスオフセットなど)が、対応する「通し番号」としての数値に変換され、数値項目に格納されます。
解決策2:比較したい数値を USAGE IS INDEX 項目へ変換して比較する
SET USAGE-IS-INDEX項目 TO 数値項目.
IF USAGE-IS-INDEX項目 = 別のUSAGE-IS-INDEX項目 THEN ...
この方法は、比較対象が元々数値であっても、一旦USAGE IS INDEX項目に変換してから、別のUSAGE IS INDEX項目と比較する形になります。
どちらの方法でも比較は可能ですが、通常は「解決策1」の方が直感的でデバッグもしやすいでしょう。
4. サンプルプログラム
実際にコードで確認してみましょう。このコードをコピー&ペーストして、ご自身の環境で試してみてください。
IDENTIFICATION DIVISION.
PROGRAM-ID. INDEX-COMPARE-SAMPLE.
AUTHOR. COBOL-VETERAN.
DATA DIVISION.
WORKING-STORAGE SECTION.
> テーブル(配列)の定義
> OCCURS 10 TIMES で10個の要素を持つ配列を定義し、
> INDEXED BY WS-IDX で添字として WS-IDX を使用することを宣言
01 WS-DATA-TABLE.
05 WS-ITEM OCCURS 10 TIMES INDEXED BY WS-IDX.
10 WS-VALUE PIC X(05).
> USAGE IS INDEX 項目の定義
> 配列の添字や、別のINDEX項目との比較に使用
01 WS-INDEX-FIELD USAGE IS INDEX.
> 通常の数値項目(比較対象として使用)
01 WS-NORMAL-NUMBER PIC 9(02) VALUE 5.
> 作業用INDEX項目
> 数値項目からINDEX項目への変換で使用
01 WS-WORK-INDEX USAGE IS INDEX.
> 作業用数値項目
> INDEX項目から数値項目への変換で使用
01 WS-WORK-NUMBER PIC 9(02).
PROCEDURE DIVISION.
MAIN-PARA.
DISPLAY "--- USAGE IS INDEX の比較制限サンプル ---"
DISPLAY " "
> ----------------------------------------------------
> 1. INDEX項目同士の比較 (これはOK)
> ----------------------------------------------------
SET WS-IDX TO 3. > WS-IDX に値 3 をセット
SET WS-INDEX-FIELD TO 3. > WS-INDEX-FIELD に値 3 をセット
IF WS-IDX = WS-INDEX-FIELD THEN
DISPLAY "1. WS-IDX と WS-INDEX-FIELD は等しい (INDEX同士)"
ELSE
DISPLAY "1. WS-IDX と WS-INDEX-FIELD は等しくない (INDEX同士)"
END-IF.
DISPLAY " "
> ----------------------------------------------------
> 2. INDEX項目と通常の数値項目を直接比較 (これはNG!コンパイルエラーになる)
> 実際に試す場合は、このブロックのコメントアウトを外してください。
> コンパイラによってはエラーメッセージが異なりますが、型不一致のエラーになります。
> ----------------------------------------------------
> DISPLAY "2. INDEX項目と数値の直接比較 (これはコンパイルエラーになります)"
> IF WS-IDX = WS-NORMAL-NUMBER THEN > ここでエラー!
> DISPLAY "直接比較はできません"
> ELSE
> DISPLAY "直接比較はできません"
> END-IF.
> DISPLAY " "
> ----------------------------------------------------
> 3. INDEX項目を数値項目へSETして比較 (これが推奨される方法)
> ----------------------------------------------------
SET WS-IDX TO 5. > WS-IDX に比較したい値 5 をセット
SET WS-WORK-NUMBER TO WS-IDX. > WS-IDX の内容を数値項目 WS-WORK-NUMBER へ変換
> (例: 内部的に保持しているアドレス値を「5」という数値に変換)
IF WS-WORK-NUMBER = WS-NORMAL-NUMBER THEN
DISPLAY "3. WS-IDX (数値変換後) と WS-NORMAL-NUMBER は等しい"
ELSE
DISPLAY "3. WS-IDX (数値変換後) と WS-NORMAL-NUMBER は等しくない"
END-IF.
DISPLAY " "
> ----------------------------------------------------
> 4. 数値項目をINDEX項目へSETして比較 (こちらも可能だが、少し回りくどい)
> ----------------------------------------------------
SET WS-NORMAL-NUMBER TO 5. > 比較したい数値 5 を WS-NORMAL-NUMBER にセット
SET WS-WORK-INDEX TO WS-NORMAL-NUMBER. > WS-NORMAL-NUMBER の内容をINDEX項目 WS-WORK-INDEX へ変換
> (例: 数値「5」を内部的なアドレス値に変換)
SET WS-IDX TO 5. > WS-IDX に比較したい値 5 をセット
IF WS-IDX = WS-WORK-INDEX THEN
DISPLAY "4. WS-IDX と WS-NORMAL-NUMBER (INDEX変換後) は等しい"
ELSE
DISPLAY "4. WS-IDX と WS-NORMAL-NUMBER (INDEX変換後) は等しくない"
END-IF.
DISPLAY " "
> ----------------------------------------------------
> 5. 配列操作の例 (PERFORM VARYING で INDEX項目を直接操作)
> この場合はSET命令は不要で、内部的に最適化された高速な処理が行われる
> ----------------------------------------------------
DISPLAY "5. 配列に値を設定する例:"
PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 10
MOVE "ITEM-" & WS-IDX TO WS-VALUE(WS-IDX)
DISPLAY " WS-VALUE(" & WS-IDX & ") = " WS-VALUE(WS-IDX)
END-PERFORM.
DISPLAY " "
> 特定の要素を表示(WS-NORMAL-NUMBERの値を利用)
SET WS-IDX TO WS-NORMAL-NUMBER. > WS-NORMAL-NUMBER (5) の値を WS-IDX にセット
DISPLAY "5. WS-NORMAL-NUMBER (5) に対応する WS-ITEM の値: " WS-VALUE(WS-IDX).
STOP RUN.
5. 応用・注意点:現場で役立つ補足情報
- SET命令の重要性:
USAGE IS INDEX 項目と数値項目の間で値をやり取りする唯一の標準的な方法が SET命令 です。MOVE命令では型変換が行われず、期待通りの動作をしないか、コンパイルエラーになります。 - パフォーマンスへの影響:
USAGE IS INDEX 項目は、その特性上、非常に高速な配列アクセスを可能にします。しかし、比較のために一旦数値項目に変換すると、その変換処理分のオーバーヘッドが発生します。ループ処理などで頻繁に比較を行う場合は、この点を考慮に入れる必要があります。 - PERFORM VARYING と INDEX項目:
PERFORM VARYING 句で添字に INDEXED BY で定義した項目(例:PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 10)を指定すると、COBOLコンパイラが内部的に最適化された高速なループ処理を行います。この場合、明示的な SET命令 は不要です。 - 添字の範囲チェック:
通常の数値添字であれば、配列の範囲を超えたアクセスは実行時エラー(SUBSCRIPT OUT OF RANGE など)になることが多いですが、USAGE IS INDEX 項目は物理アドレスを直接操作するため、範囲外アクセスでもエラーにならず、不正なメモリー領域を読み書きしてしまう危険性があります。配列処理の際は、添字が必ず配列の範囲内にあるか、ロジックで厳密にチェックするよう心がけましょう。 - デバッグの難しさ:
USAGE IS INDEX 項目の値は、デバッガで直接見ても数値として表示されないことがあります(メモリーアドレスや内部表現値として表示される)。デバッグ中に正確な値を確認したい場合は、一旦 SET命令 で数値項目に変換してから表示させると良いでしょう。
今回の豆知識で、USAGE IS INDEX 項目の比較制限とその対処法について、ご理解いただけたでしょうか。この知識は、COBOLプログラミングにおけるバグの温床を一つ減らし、より堅牢で効率的なコードを書くために非常に役立ちます。ぜひ、日々の開発に活かしてください!

コメント