OCCURS and subscripts
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
OCCURSgoes 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. OCCURSon a group (WS-STAFFabove) 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.