お問い合わせ

ホーム > COBOLのはなし > 誤りがあるプログラムの動作

誤りがあるプログラムの動作

山本 剛司(富士通 ミドルウェア事業本部)

COBOLの言語仕様はISO(国際規格)やJIS(日本工業規格)で定められており、各COBOLベンダーはこれらの規格にあったコンパイラを提供しています。ただし、規格は厳密ではなく、実装者定義としているところは各COBOLベンダーが言語仕様を決めています。

言語仕様では誤った使い方をした場合の動作を定めていません。プログラムに誤りがある場合、翻訳する環境または実行する環境が変わったときにプログラムの動作が変わることがあります。特に各ベンダーの言語仕様に「結果は規定されない」と明記されている記述や、動作が明記されていない記述の場合、期待どおりの動作をしないことがあります。

本稿では、期待どおりに動作するとは限らない仕様を使った誤りのあるプログラムと対処方法について説明します。

1. 桁あふれ

数字項目を定義する際、PICTURE文字列に "9" を使用して格納する十進数の数値の最大桁数を記述します。例えば、整数を十進数で最大4桁格納することができる項目を定義する場合には以下のように記述します。

01 DATA-1 PIC 9(4).

上記では、0から9999までの値を格納することができる数字項目DATA-1を定義したことになります。

注意すべきところは、0から9999以外の値を格納した場合にどのように動作するか規定されていないことです。

1.1 転記による桁あふれ

■問題点■

言語仕様では、数字項目への転記は「受取り側の小数点の位置に合わせて格納され、端にゼロが補われたり、端が切り捨てられたりします。」と明記されています。

桁数の多い項目から桁数の少ない項目への転記では、送出し側の転記の対象とならない桁を切り捨てて受取り側に転記する必要があります。実行時の処理を高速化するため、コンパイラが切り捨てる処理を省略する場合があります。この場合、転記の対象とならない桁も転記されるため言語仕様どおりの動作をしないことがあります。

01  A PIC 9(4) BINARY.    ← 2進4桁(2バイト)
01  B PIC 9(3) BINARY.    ← 2進3桁(2バイト)

  MOVE 1234 TO A      
  MOVE A TO B             ← 4桁の項目から3桁への項目への転記で桁が取り除かれず
                             1234が転記されることがある

  DISPLAY B               ← Bとして3桁を表示する
  IF B = 234 THEN         ← Bと234を比較する
    DISPLAY "OK" 
  ELSE 
    DISPLAY "NG"
  END-IF

実行結果:

234        ← 3桁が表示される
NG         ← 234と1234の比較のため偽となる

上記の問題は、2進形式の数字項目には定義した桁数より大きな値を格納できてしまうことにより発生します。上記のBは3桁の2進形式の数字項目で記述しています。一般的に3桁の2進形式の領域長は2バイトです。コンパイラはDISPLAY文で表示する桁は3桁として処理していますが、IF文やMOVE文では2進2バイトとして処理しています。符号なし2進形式2バイトには0〜65535を格納できるため、4桁の値が格納されているにもかかわらず表示される桁数は3桁という現象が起きてしまいます。

■対処方法■

この問題は、送出し項目の値の桁数が、受取り項目の桁数より大きいことにより発生します。転記する値を正確に予測し、結果を格納する数字項目に十分な桁数を記述しておき、桁あふれが起きないようにしてください。
また、転記の対象とならない桁を取り除く処理を省略しないコンパイルオプションが用意されている場合、利用することができます。

1.2 桁あふれ条件が発生する演算

■問題点■

言語仕様では、算術文の実行で「演算結果が受取り項目に入りきらない場合、桁あふれ条件が発生します。桁あふれ条件が発生した場合の結果は規定されない」ことが明記されています。

そのため、演算結果が受取り項目に入りきらない値であった場合、期待する結果が得られないことがあります。

01  A PIC 9(4) BINARY.    ← 2進4桁(2バイト)

  COMPUTE A = 6789 * 10       ← 2進2バイトの項目に67890は格納できない
  DISPLAY A

実行結果:

2354        ← 規定されていない値が転記されている

算術演算の結果は67890となり、Aに格納できる4桁を超えてしまいます。 このような場合、Aに格納される値は規定されません。

■対処方法■

演算結果を正確に予測し、結果を格納する数字項目に十分な桁数を定義してください。
演算結果が予測できない場合には、桁あふれ条件を判定してください。算術文にON SIZE ERROR指定を記述することにより、桁あふれ条件の発生時の動作を記述することができます。

COMPUTE A = 6789 * 10
   ON SIZE ERROR DISPLAY "桁あふれ発生"
END-COMPUTE

実行結果:

桁あふれ発生

上記のように、プログラムの記述によって桁あふれ条件を検出することができます。

2. 数字項目の定義と矛盾する参照・転記

■問題点■

数字項目には、外部10進項目、内部10進項目、2進項目などがあり、格納する値の内部表現の形式が決まっています。
外部10進(ゾーン形式)項目は1バイトごとに1桁の数値を格納し、数字部、ゾーン部、符号部として値を格納します。
内部10進(パック形式)項目は4ビットごとに1桁の数値を格納し、数字部、符号部として値を格納します。
2進項目には項目全体に数値を2の補数形式で格納します。

コンパイラは10進項目には正しい形式で値が格納されているものとして処理しています。

正しい形式の値が格納されていない状態で10進項目が参照された場合、結果は規定されません。

01  A PIC 9(4) DISPLAY.
01  B PIC 9(4) BINARY.

【 Aに'123+' が入っているとします。】

  MOVE A TO B
  DISPLAY B

実行結果:

1241

Aの項目の数字部4桁にはそれぞれ'0'〜'9'が格納されている必要があります。'+'は不正な値であるため、 Bに転記される値は規定されません。

■対処方法■

数字項目は、正しい値が格納されている状態で参照するようにしてください。

正しい形式の値が格納されているか不明な場合には、プログラムの記述によって正しい値が格納されていることを判定してください。字類条件を使用することにより数字項目に格納されている値が正しい形式であるかを判定できるため、正しい形式で格納されていないときの動作を記述することができます。

手入力や、正当性の確認できないファイルからの入力など正しい形式の値が格納されていない可能性がある場合には、字類条件による判定を行ってから処理を行ってください。

 IF A IS NUMERIC THEN    ← 数字として正しい形式かを判定
   MOVE A TO B
 ELSE
   DISPLAY "Aに数字以外が格納されています"
 END-IF

実行結果:

Aに数字以外が格納されています

上記のように、プログラムの記述によって正しくない値を検出することができます。
また、数字項目に数字以外が格納されている場合に異常を検出するコンパイルオプションが用意されている場合、利用することができます。

3. 転記の送出しと受取りの領域の重複

■問題点■

転記では、送出し側の値が受取り側の項目に転記されます。 送出し側の領域と受取り側の領域に重なりがあることは考慮されません。そのため、送出し側と受取り側の領域が重なっている場合、転記の結果は規定されません。

01  DATA1  PIC X(10).

  MOVE "ABCDEFGHIJ" TO DATA1
  MOVE DATA1(1:9) TO DATA1(2:9)
  DISPLAY DATA1

実行結果:

AABCDDFGHH

上記のプログラムではDATA1のデータを1バイト右にずらして"AABCDEFGHI"を期待している、あるいは1バイト単位での転記を9回行って"AAAAAAAAAA"になることを期待しているかも知れません。しかしながら、言語仕様では送出し側と受取り側の項目が重なっている場合の転記の結果は規定されません。

■対処方法■

プログラムを検証して、送出し側と受取り側の項目に領域の重なりが無いことを確認してください。

4. プログラム呼出しにおける引数の不一致

■問題点■

プログラムに引数を指定して、他のプログラムを呼び出すことができます。
プログラム呼出しにおいて、呼出し元に指定している引数の個数・長さが、呼出し先に指定している引数の個数・長さと 一致していない場合、正しく動作しないことがあります。

呼出し元

    PROGRAM-ID. PG1.

    WORKING-STORAGES SECTION.
    01  WK1  PIC X(10).

      CALL "SUBPG1" USING WK1.     ← 引数に10バイトの項目
呼出し先

    PROGRAM-ID. SUBPG1.

    LINKAGE SECTION.
    01  WK1  PIC X(20).
  
    PROCEDURE DIVISION USING WK1.  ← 実際には引数に10バイトの項目
  
      MOVE SPACE TO WK1.           ← 20バイトを設定

上記プログラムでは呼出し元の引数WK1は10バイトの長さしかありませんが、呼出し先では20バイトの長さと定義しているため、呼出し元の領域を破壊します。

■対処方法■

プログラムを検証して、呼出し元と呼出し先の引数の個数・長さを一致させてください。
また、呼出し元と呼出し先の引数の個数・長さが一致していないときに誤りを検出するコンパイルオプションが用意されている場合、利用することができます。

5. 初期化していない項目の参照

■問題点■

データ項目の定義において、VALUE句を用いて初期値を指定することができます。 VALUE句を記述していない場合の初期値は規定されていません。そのため、何も設定していない状態でデータ項目を参照した場合の結果は規定されません。

01  A  PIC 9(4) DISPLAY.      ← 初期値なし
01  B  PIC 9(4) DISPLAY. 

  COMPUTE B = A + 1. 

Aに値が設定されていないため、A+1の演算結果は不定となり、Bに何が格納されるかは規定されません。

■対処方法■

データ項目を参照する場合は、必ず値を設定してから参照してください。

6. 集団項目の誤った初期化

6.1 集団項目にLOW-VALUEを転記し10進項目と0を比較

集団項目にLOW-VALUEを転記した場合、その集団項目に含まれるすべての基本項目の占める各バイトにはLOW-VALUEが転記されます。LOW-VALUEの値として0を意味する値が使用されていた場合、集団項目に含まれる外部10進項目および内部10進項目の数字桁、符号桁には0が格納されます。この状態で、外部10進項目および内部10進項目を参照した場合に0とみなされることが多いですが、符号部には不正な値が格納されているためコンパイラによっては0と一致しないことがあります。

01  GP1.
  02  WKX  PIC  X(4).
  02  WK1  PIC  S9(4) DISPLAY.
  02  WK2  PIC  S9(4) PACKED-DECIMAL.
  02  WK3  PIC  S9(4) BINARY.

  MOVE LOW-VALUE TO GP1

  IF WKX = LOW-VALUE THEN   ← 英数字項目はLOW-VALUEと一致する
    ....

  IF WK1 = ZERO  THEN       ← 外部10進項目は0と一致すると規定されていない
    ....

  IF WK2 = ZERO  THEN       ← 内部10進項目は0と一致すると規定されていない
    ....

  IF WK3 = ZERO  THEN       ← 2進項目は0と一致する
    ....

■対処方法■

集団項目を初期化する際、LOW-VALUEを転記するのではなく、INITIALIZE文で初期化してください。 INITIALIZE文は、各基本項目の形式に合わせた初期値を格納します。

  INITIALIZE  GP1      ← 形式に合わせて値が設定されます

6.2 集団項目に空白を転記し10進項目と0を比較

■問題点■

集団項目に空白を転記した場合、その集団項目に含まれるすべての基本項目の占める各バイトには空白が転記されます。前述した「集団項目にLOW-VALUEを転記し10進項目と0を比較」と同様です。集団項目に空白を転記した状態で、外部10進項目および内部10進項目と0を比較した場合、 期待通りの動作をしないことがあります。

01  GP1.
  02  WKX  PIC  X(4).
  02  WK1  PIC  S9(4) DISPLAY.
  02  WK2  PIC  S9(4) PACKED-DECIMAL.
  02  WK3  PIC  S9(4) BINARY.

  MOVE SPACE TO GP1

  IF WKX = SPACE THEN       ← 英数字項目は空白と一致する
    ....

  IF WK1 = ZERO  THEN       ← 外部10進項目は0と一致すると規定されていない
    ....

  IF WK2 = ZERO  THEN       ← 内部10進項目は0とは一致しない
    ....

  IF WK3 = ZERO  THEN       ← 2進項目は0とは一致しない
    ....

■対処方法■

集団項目を初期化する際、空白を転記するのではなく、INITIALIZE文で初期化してください。

  INITIALIZE  GP1      ← 基本項目の形式に合わせて値が転記されます

  IF WK1 = ZERO  THEN
    ....

6.3 集団項目に空白を転記し日本語項目と空白の比較

■問題点■

集団項目に空白を転記した場合、その集団項目に含まれるすべての基本項目の占める各バイトには英数字項目の空白が転記されます。日本語項目にも英数字の空白が転記されます。コード系に依存しますが、英数字の空白と日本語の空白は同じではないことが多いです。集団項目に空白を転記した状態で、日本語項目と空白を比較した場合、期待通りの動作をしないことがあります。

01  GP1.
  02  WKX  PIC  X(4).
  02  WKN  PIC  N(4).

  MOVE SPACE TO GP1      ← 集団項目に空白を転記する

  IF WKX = SPACE THEN       ← 英数字項目は英数字の空白と一致する
    ....

  IF WKN = SPACE THEN       ← 日本語項目は日本語の空白と一致しないことがある
    ....

■対処方法■

集団項目を初期化する際、空白を転記するのではなく、INITIALIZE文で初期化してください。

  INITIALIZE  GP1      ← 基本項目の形式に合わせて値が転記されます

  IF WKX = SPACE THEN
    ....
  IF WKN = SPACE THEN
    ....

7. 添字の誤り

■問題点■

データ項目を定義する際、OCCURES句に繰返し回数を指定することにより、配列を定義することができます。手続き部において、OCCURES句に指定した繰返し回数より大きい添字を指定した場合、領域外参照となるため結果は規定されません。

01  A   PIC 9(4) VALUE 20.
01  GP1.
  02  WK1 OCCURS 10 PIC 9(4).  ← 外部10進4桁の数字項目を10個定義している。

  DISPLAY WK1(A).      ← 添字として20が指定されている。

■対処方法■

添字に指定する値はデータ項目を定義したときにOCCURES句に指定したの繰返し回数以下にしてください。
また、添字の誤りを検出するコンパイルオプションが用意されている場合、利用することができます。

8. 部分参照の誤り

■問題点■

部分参照はデータ項目の一部分を対象として値を設定したり参照したりする機能です。データ項目に最左端文字位置と長さを指定することによりデータ項目の一部分を使用することができます。最左端文字位置と長さがデータ項目の領域外を参照する値となった場合の結果は規定されません。

01  A    PIC 9(4)  VALUE 10.
01  STR  PIC X(10) VALUE "ABCDEFGHIJ".


  DISPLAY STR(3:A).      ← Aには10が入っているため領域外が参照される

実行結果:

CDEFGHIJ??   ← ??の文字は不定

■対処方法■

部分参照に指定する値は領域内を参照する値を指定してください。
また、部分参照の誤りを検出するコンパイルオプションが用意されている場合、利用することができます。

9. レコード領域の使用法誤り

■問題点■

ファイルを扱う場合、ファイルのレコード領域を定義する必要があります。このレコード領域は、ファイルがオープンしている間にのみ使用することができます。ファイルのオープン前及びクローズ後にレコード領域を使用すると異常終了することがあります。

FD  FILE1.
01  REC1 PIC X(10).

  MOVE SPACE TO REC1   ← オープン前にレコード領域を使用

  OPEN OUTPUT FILE1

■対処方法■

ファイルのレコード領域はファイルをオープンしているときのみ使用してください。

まとめ

COBOLの言語仕様では誤った使い方をした場合の動作は規定されていません。プログラムに不適切な記述があった場合、翻訳する環境または実行する環境が変わったときにプログラムの実行結果が変わることがあります。

プログラム作成時には、処理論理を十分に検証し、データ項目の内容が不正確な場合には誤り検出を行うなど 誤った結果を出力しないようにしておく必要があります。