MODULE 6 · TABLES · 1/8

OCCURS and subscripts

14 min10 XPQuiz

Business data is full of repeating items: twelve months of sales, fifty states, the lines of an invoice. Declaring WS-JAN-SALES, WS-FEB-SALES and so on is tedious and makes loops impossible. COBOL's answer is the table, what other languages call an array, declared with OCCURS.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. OCCDEMO.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-MONTHLY-SALES.
           05  WS-SALES       PIC 9(5)V99 OCCURS 12 TIMES.
       01  WS-STAFF-TABLE.
           05  WS-STAFF       OCCURS 3 TIMES.
               10  WS-STAFF-ID    PIC X(4).
               10  WS-STAFF-PAY   PIC 9(5)V99.
       01  WS-M               PIC 9(2).
       PROCEDURE DIVISION.
           MOVE 1500.00 TO WS-SALES(1)
           MOVE 2250.50 TO WS-SALES(2)
           MOVE 3 TO WS-M
           MOVE 999.99 TO WS-SALES(WS-M)
           DISPLAY "JAN: " WS-SALES(1)
           DISPLAY "MAR: " WS-SALES(WS-M)
           MOVE "E001" TO WS-STAFF-ID(2)
           MOVE 2800.00 TO WS-STAFF-PAY(2)
           DISPLAY WS-STAFF(2)
           DISPLAY "FEB: " WS-SALES(WS-M - 1)
           STOP RUN.

Output:

JAN: 01500.00
MAR: 00999.99
E0010280000
FEB: 02250.50

The rules

  • OCCURS goes on a level 02–49 item, never on an 01. The 01 is the record that contains the table.
  • A subscript in round brackets picks the element. It can be a literal, a numeric data item, or a data item plus or minus a literal (WS-M - 1).
  • Subscripts start at 1, not 0. A literal WS-SALES(0) is a compile error.
  • OCCURS on a group (WS-STAFF above) repeats the whole group. You subscript the field you want: WS-STAFF-ID(2). WS-STAFF(2) is the whole 11-byte element, which is why its DISPLAY shows the ID and the pay run together.

How a table sits in memory

A table is just consecutive bytes. WS-STAFF-TABLE is three 11-byte elements back to back, 33 bytes in all. To find WS-STAFF-PAY(2), the program adds (2 - 1) * 11 + 4 to the start address of the table. That is all a subscript is.

Out-of-range subscripts

Because a subscript is just an address calculation, nothing stops you going past the end. Most production code is compiled without runtime checks, for speed. Watch what happens:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. OVERRUN.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-DATA.
           05  WS-SCORE       PIC 9(3) OCCURS 3 TIMES.
           05  WS-TOTAL       PIC 9(5) VALUE ZERO.
       01  WS-I               PIC 9(2).
       PROCEDURE DIVISION.
           MOVE 0 TO WS-TOTAL
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4
               MOVE 100 TO WS-SCORE(WS-I)
           END-PERFORM
           DISPLAY "TOTAL: " WS-TOTAL
           STOP RUN.

The loop runs to 4, but the table has only 3 elements. WS-SCORE(4) is the first three bytes of WS-TOTAL, so the program prints TOTAL: 10000 even though nothing was ever added to it. No error, no abend: just a wrong number that turns up somewhere else in the program, possibly much later.

Loop limit = table size

Every PERFORM VARYING over a table must stop at the table's size. When a table is loaded from input, check the count before storing, or one extra input record will corrupt your working storage.

On the job

IBM's compiler option SSRANGE (GnuCOBOL: -debug) adds runtime range checks and abends on a bad subscript instead of corrupting data. Many shops turn it on in test and off in production. If a program gives "impossible" results, recompiling it with SSRANGE is one of the first things to try.

Check your understanding

1. Given 05 WS-SALES PIC 9(5)V99 OCCURS 12 TIMES., which reference is the first element?
2. Why can't OCCURS appear on an 01-level item?
3. A table has OCCURS 3 TIMES and the program moves a value to element 4, with no runtime checking. What usually happens?
4. Each element of 05 WS-STAFF OCCURS 3 TIMES contains a PIC X(4) and a PIC 9(5)V99 field. How many bytes does the whole table take?