Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

COBOL  Module 6 – Tables: OCCURS, SEARCH and SORT

Tables, subscripts and indexes

Define one- and two-dimensional COBOL tables with OCCURS, load them from literals or a file, use subscripts and indexes, and catch out-of-range entries.

  • Intermediate
  • 15 minutes
  • Examples run with GnuCOBOL 3.2 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Define one- and two-dimensional tables with OCCURS and load them from literals or files
  • Choose between subscripts and indexes and move indexes with SET
  • Detect out-of-range subscripts with runtime checks and explain what happens without them
  • Check a table's capacity before loading data that could exceed it

Before you start

What you’ll learn · Before you start

What you will learn

  • Define one- and two-dimensional tables with OCCURS and load them from literals or files
  • Choose between subscripts and indexes and move indexes with SET
  • Detect out-of-range subscripts with runtime checks and explain what happens without them
  • Check a table's capacity before loading data that could exceed it

Before you start

On this page

Writing entry 6 of a five-entry branch table changed the field stored after the table from UNTOUCHED to MUMBAIHED, and the program ended normally. A COBOL table is a run of entries in storage with nothing around it, and only the program, or a check it was built with, keeps a subscript: A number in brackets after a table item that picks one entry, counting from 1, as in BR-CODE(3). It is a literal or a whole-number data item, optionally plus or minus a constant. inside it.

Below you will define tables with OCCURS in one and two dimensions, load them from literals and from a file without overfilling them, choose between subscripts and indexes, and watch what an out-of-range subscript does with and without run-time checks.

OCCURS repeats an entry, and a subscript picks one, counting from 1

The OCCURS clause: The clause that makes an item a table: ITEM OCCURS n TIMES describes n entries of the same layout, one after another, numbered from 1. It is not allowed at levels 01, 66, 77 or 88., in IBM’s words, “specifies tables whose elements can be referred to by indexing or subscripting”. BR-ENTRY OCCURS 5 TIMES describes five entries of the same layout, one after the other, and BR-CODE(3) names the code of the third. IBM’s guide says “the lowest possible subscript value is 1, which references the first occurrence”, and the subscript may be a literal or a numeric data item. OCCURS is not allowed at level 01, 66, 77 or 88, so a table is always part of a group. This program lays out the values of five branches as literals and describes the table over them with REDEFINES:

branches.cob: a table of five branches over literal values COB · branches.cob
       IDENTIFICATION DIVISION.
       PROGRAM-ID. BRANCHES.
      * A table of five branches, its values laid out as literals
      * and the table described over them with REDEFINES. A
      * subscript picks an entry, counting from 1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  BRANCH-VALUES.
           05  FILLER   PIC X(12) VALUE "BR01PUNE    ".
           05  FILLER   PIC X(12) VALUE "BR02CHENNAI ".
           05  FILLER   PIC X(12) VALUE "BR03KOLKATA ".
           05  FILLER   PIC X(12) VALUE "BR04JAIPUR  ".
           05  FILLER   PIC X(12) VALUE "BR05INDORE  ".
       01  BRANCH-TABLE REDEFINES BRANCH-VALUES.
           05  BR-ENTRY OCCURS 5 TIMES.
               10  BR-CODE  PIC X(4).
               10  BR-CITY  PIC X(8).
       01  SUB          PIC 99.
       PROCEDURE DIVISION.
           DISPLAY "table bytes: " FUNCTION LENGTH(BRANCH-TABLE)
           DISPLAY "entry bytes: " FUNCTION LENGTH(BR-ENTRY(1))
           PERFORM VARYING SUB FROM 1 BY 1 UNTIL SUB > 5
               DISPLAY "entry " SUB ": " BR-CODE(SUB) " " BR-CITY(SUB)
           END-PERFORM
           MOVE 3 TO SUB
           DISPLAY "BR-CITY(SUB) with SUB = 3: " BR-CITY(SUB)
           STOP RUN.

The session also runs the next section’s two programs:

A table of branches, a table of sales, and an ALL subscript Python · tables_session.py
from session import files, run

# A one-dimensional table loaded from literals, a two-dimensional table filled by a loop, and the ALL subscript,
# which GnuCOBOL 3.2 does not accept.
files("branches.cob", "sales.cob", "sumall.cob")
run("""
cobc -x branches.cob && ./branches
cobc -x sales.cob && ./sales
!cobc -x sumall.cob
""")

Output

$ cobc -x branches.cob && ./branches
table bytes: 60
entry bytes: 12
entry 01: BR01 PUNE
entry 02: BR02 CHENNAI
entry 03: BR03 KOLKATA
entry 04: BR04 JAIPUR
entry 05: BR05 INDORE
BR-CITY(SUB) with SUB = 3: KOLKATA
$ cobc -x sales.cob && ./sales
table bytes: 42
branch 1 total:   3750.00
branch 2 total:   6750.00
MONTH-AMT(2, 3):   2375.00
MONTH-AMT(1, 4), a month too far:   2125.00
$ cobc -x sumall.cob
sumall.cob:11: error: syntax error, unexpected ALL
    9 |        01  TOTAL        PIC 9(7)V99.
   10 |        PROCEDURE DIVISION.
   11 >            COMPUTE TOTAL = FUNCTION SUM(AMT(ALL))
   12 |            DISPLAY TOTAL
   13 |            STOP RUN.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 tables_session.py

The table is 60 bytes, five entries of 12, and the loop read them with SUB from 1 to 5. The entries have no separator and no length of their own: entry n simply starts 12 bytes after entry n - 1, which is all a subscript works out.

The REDEFINES pattern is common in old programs because the values sit next to the layout. IBM’s guide suggests VALUE clauses when a table holds stable values “such as days and months”, and reading the values into the table before the program refers to it when they change from one run to the next, which a section below does.

A table inside a table takes two subscripts

An entry can itself contain a table. In sales.cob, BRANCH-ROW OCCURS 2 TIMES and each row holds MONTH-AMT OCCURS 3 TIMES, so MONTH-AMT takes two subscripts, the branch first and then the month: MONTH-AMT(2, 3) is the third month of the second branch. A nested VARYING loop stored B x 1000 + M x 125 in each entry, which gives the amounts below; the session printed the two row totals and MONTH-AMT(2, 3), 2375.00:

MONTH-AMT Month 1 Month 2 Month 3 Row total
Branch 1 1125.00 1250.00 1375.00 3750.00
Branch 2 2125.00 2250.00 2375.00 6750.00

The table is 42 bytes: 2 rows of 3 amounts of 7 bytes each, the rows one after the other. The month varies fastest in storage, as MONTH-NO varied fastest in the VARYING … AFTER loop of the loops lesson. That is why the last line of the program, MONTH-AMT(1, 4), printed 2125.00: there is no fourth month in row 1, so the address fell on the first month of row 2, and no error came.

IBM’s guide offers a shorter way to total a table: for intrinsic functions with several arguments, “you can use the subscript ALL to reference all the items in the table”, as in FUNCTION SUM(AMT(ALL)). GnuCOBOL 3.2 does not accept it: sumall.cob stopped with syntax error, unexpected ALL. A loop with an accumulator, as in sales.cob, works with both compilers.

ALL subscripts and GnuCOBOL

Code copied from a z/OS program that totals a table with FUNCTION SUM(TABLE-NAME(ALL)) will not compile with GnuCOBOL 3.2. Replace the call with a PERFORM VARYING loop that adds each entry.

Checkpoint

Check: OCCURS and subscripts from 1 · Two-dimensional tables

  1. Question 1 of 2 BR-ENTRY OCCURS 5 TIMES, and each entry is 12 bytes. At which byte of the table does BR-ENTRY(4) start?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 37

    Entry n starts 12 bytes after entry n - 1, at byte 12 x (n - 1) + 1, so entry 4 starts at byte 37 and ends at byte 48.

  2. Question 2 of 2 In sales.cob, which amount is MONTH-AMT(2, 3)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The third month of the second branch, 2375.00

    MONTH-AMT(B, M) is month M of branch B. In storage it starts at byte 21 x (B - 1) + 7 x (M - 1) + 1.

Load a table from a file, and check that it fits

Values that change, such as the list of branches, belong in a file that the program reads into the table when it starts: a new branch is then a new line of data, not a change to the program. The loop has one duty besides storing, which is to refuse a file with more lines than the table has entries. This program loads the five-entry table from branches.txt, first a file of three branches, then one of six:

loadfile.cob: the branch table loaded from a file COB · loadfile.cob
       IDENTIFICATION DIVISION.
       PROGRAM-ID. LOADFILE.
      * Loads the branch table from a file at start-up and refuses
      * a file with more branches than the table holds. The file
      * statements are the subject of a later lesson.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT BRANCH-FILE ASSIGN TO "branches.txt"
               ORGANIZATION LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  BRANCH-FILE.
       01  BRANCH-REC   PIC X(12).
       WORKING-STORAGE SECTION.
       01  BRANCH-TABLE.
           05  BR-ENTRY OCCURS 5 TIMES.
               10  BR-CODE  PIC X(4).
               10  BR-CITY  PIC X(8).
       01  BR-COUNT     PIC 99 VALUE 0.
       01  BR-MAX       PIC 99 VALUE 5.
       01  EOF-FLAG     PIC X VALUE "N".
           88  END-OF-FILE  VALUE "Y".
       01  FULL-FLAG    PIC X VALUE "N".
           88  TABLE-FULL   VALUE "Y".
       01  REFUSED-CODE PIC X(4).
       01  SUB          PIC 99.
       PROCEDURE DIVISION.
           OPEN INPUT BRANCH-FILE
           PERFORM UNTIL END-OF-FILE
               READ BRANCH-FILE
                   AT END SET END-OF-FILE TO TRUE
                   NOT AT END PERFORM STORE-BRANCH
               END-READ
           END-PERFORM
           CLOSE BRANCH-FILE
           IF TABLE-FULL
               DISPLAY "too many branches: " REFUSED-CODE
                   " does not fit a table of " BR-MAX
               MOVE 8 TO RETURN-CODE
               STOP RUN
           END-IF
           DISPLAY BR-COUNT " branches loaded"
           PERFORM VARYING SUB FROM 1 BY 1 UNTIL SUB > BR-COUNT
               DISPLAY "  " BR-CODE(SUB) " " BR-CITY(SUB)
           END-PERFORM
           STOP RUN.
       STORE-BRANCH.
           IF BR-COUNT = BR-MAX
               MOVE BRANCH-REC(1:4) TO REFUSED-CODE
               SET TABLE-FULL TO TRUE
               SET END-OF-FILE TO TRUE
           ELSE
               ADD 1 TO BR-COUNT
               MOVE BRANCH-REC TO BR-ENTRY(BR-COUNT)
           END-IF.
A file of three branches, then a file of six Python · loadfile_session.py
from session import files, run, write

# The branch table loaded from a file of three branches, then from a file of six, one more than the table holds.
files("loadfile.cob")
write("three.txt", "BR01PUNE\nBR02CHENNAI\nBR03KOLKATA\n")
write("six.txt", "BR01PUNE\nBR02CHENNAI\nBR03KOLKATA\nBR04JAIPUR\nBR05INDORE\nBR06NASHIK\n")
run("""
cobc -x loadfile.cob
cp three.txt branches.txt && ./loadfile
!cp six.txt branches.txt && ./loadfile
""")

Output

$ cobc -x loadfile.cob
$ cp three.txt branches.txt && ./loadfile
03 branches loaded
  BR01 PUNE
  BR02 CHENNAI
  BR03 KOLKATA
$ cp six.txt branches.txt && ./loadfile
too many branches: BR06 does not fit a table of 05
[exit status 8]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 loadfile_session.py

Three branches loaded and printed. The file of six stopped with too many branches: BR06 does not fit a table of 05 and status 8, after five branches had been stored and before the sixth could touch anything. Step through the second run:

Interactive · stage by stage Loading a five-entry table from a file of six branches Step through the reads. The branch on the right is taken when the table is already full.
Loading a five-entry table from a file of six branches, step 4 of 4. Showing READ BRANCH-FILE, BR-COUNT = BR-MAX?, Store the branch (BR05 stored, count 5), TABLE-FULL (BR06 refused), CLOSE and report. In focus: CLOSE and report.yes: refuseREAD BRANCH-FILEthe next lineBR-COUNT = BR-MAX?is the table full?Store the branchBR05 stored, count 5TABLE-FULLBR06 refusedCLOSE and reportRETURN-CODE 8

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The first line. READ gives BR01PUNE. BR-COUNT is 0, which is not BR-MAX, so the branch is stored as entry 1 and BR-COUNT becomes 1.
  2. Up to five. The next four lines are stored the same way, BR02 to BR05, and BR-COUNT reaches 5, the size of the table.
  3. The sixth line. READ gives BR06NASHIK. Now BR-COUNT = BR-MAX is true, so STORE-BRANCH does not touch the table. It keeps BR06 in REFUSED-CODE, sets TABLE-FULL and ends the loop.
  4. The report. After CLOSE, the program prints too many branches: BR06 does not fit a table of 05 and ends with RETURN-CODE 8, so the job sees the failure. Not one byte was written past the table.

The check costs one comparison per line, and it turns a file that has outgrown the program into a failed job with a reason, instead of data written past the end of the table, which the last section of this lesson shows.

Key idea

Read changing values into a table from a file at start-up, count the entries as they are stored, and stop with an error before an entry beyond the OCCURS limit is written.

An index is set with SET and stands for a position

A subscript is an ordinary numeric item, which you MOVE and ADD to like any other. An index: A name declared with INDEXED BY on a table and changed with SET, PERFORM VARYING or SEARCH. On IBM's compiler it holds a displacement, (entry number - 1) times the entry length. is declared with the table, BR-ENTRY OCCURS 5 TIMES INDEXED BY BR-IDX, and IBM’s guide says to change it with “a PERFORM, SEARCH, or SET statement”. It also says what an index holds on IBM’s compiler: not the entry number but a displacement, “the occurrence number (subscript) minus 1”, multiplied by the entry’s length. This program works with an index:

indexes.cob: SET, SET UP BY, an offset and a conversion COB · indexes.cob
       IDENTIFICATION DIVISION.
       PROGRAM-ID. INDEXES.
      * The branch table with an index. SET gives the index a value
      * and moves it; SET into a numeric item turns it back into an
      * entry number for DISPLAY. GnuCOBOL can also DISPLAY the index
      * itself, which shows how it holds the index.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  BRANCH-VALUES.
           05  FILLER   PIC X(12) VALUE "BR01PUNE    ".
           05  FILLER   PIC X(12) VALUE "BR02CHENNAI ".
           05  FILLER   PIC X(12) VALUE "BR03KOLKATA ".
           05  FILLER   PIC X(12) VALUE "BR04JAIPUR  ".
           05  FILLER   PIC X(12) VALUE "BR05INDORE  ".
       01  BRANCH-TABLE REDEFINES BRANCH-VALUES.
           05  BR-ENTRY OCCURS 5 TIMES INDEXED BY BR-IDX.
               10  BR-CODE  PIC X(4).
               10  BR-CITY  PIC X(8).
       01  ENTRY-NO     PIC 99.
       PROCEDURE DIVISION.
           SET BR-IDX TO 1
           DISPLAY "SET BR-IDX TO 1:     " BR-CODE(BR-IDX) " "
               BR-CITY(BR-IDX)
           SET BR-IDX UP BY 2
           DISPLAY "SET BR-IDX UP BY 2:  " BR-CODE(BR-IDX) " "
               BR-CITY(BR-IDX)
           DISPLAY "relative, BR-IDX + 1: " BR-CODE(BR-IDX + 1)
           SET ENTRY-NO TO BR-IDX
           DISPLAY "SET ENTRY-NO TO BR-IDX: " ENTRY-NO
           DISPLAY "BR-IDX itself:          " BR-IDX
           PERFORM VARYING BR-IDX FROM 4 BY 1 UNTIL BR-IDX > 5
               DISPLAY "VARYING BR-IDX:      " BR-CODE(BR-IDX)
           END-PERFORM
           STOP RUN.
An index set, moved and turned into a number Python · indexes_session.py
from session import files, run

# An index set, moved, used with an offset, turned into a number, shown as GnuCOBOL holds it, and varied by PERFORM.
files("indexes.cob")
run("""
cobc -x indexes.cob && ./indexes
""")

Output

$ cobc -x indexes.cob && ./indexes
SET BR-IDX TO 1:     BR01 PUNE
SET BR-IDX UP BY 2:  BR03 KOLKATA
relative, BR-IDX + 1: BR04
SET ENTRY-NO TO BR-IDX: 03
BR-IDX itself:          +000000003
VARYING BR-IDX:      BR04
VARYING BR-IDX:      BR05

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 indexes_session.py

SET BR-IDX TO 1 named BR01, and SET BR-IDX UP BY 2 moved it to BR03. BR-CODE(BR-IDX + 1) read the entry after it, BR04, without changing BR-IDX. SET ENTRY-NO TO BR-IDX converted the index back into an entry number, 3, for DISPLAY, and PERFORM VARYING moved the index through entries 4 and 5. GnuCOBOL also let the program display BR-IDX itself, and it printed +000000003: GnuCOBOL holds the entry number, where IBM’s formula gives (3 - 1) x 12 = 24 for the same entry.

Subscript Index
Declared as Any whole-number numeric item INDEXED BY on the OCCURS
Changed with MOVE, ADD, COMPUTE, VARYING SET, VARYING, SEARCH
Holds, on IBM’s compiler The entry number, from 1 A displacement, (n - 1) x entry length
Needed by Any table reference SEARCH and SEARCH ALL, next lesson

Displacements are IBM's way

GnuCOBOL keeps an index as the entry number, as BR-IDX showed, while IBM’s compiler keeps a displacement, so code that treats an index as a number can give different results on the two. Change indexes only with SET, VARYING and SEARCH, as IBM’s guide says, and use SET to turn one into a number.

Checkpoint

Check: Loading a table and checking that it fits · Indexes and SET

  1. Question 1 of 2 loadfile.cob's table has 5 entries, and its input file has 6 lines. What did the program do with the sixth?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It refused it, closed the file and ended with return code 8

    The session printed too many branches: BR06 does not fit a table of 05, with status 8. A count checked before each store is what keeps a load inside its table.

  2. Question 2 of 2 After SET BR-IDX TO 1 and SET BR-IDX UP BY 2, which branch does BR-CODE(BR-IDX + 1) give?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: BR04

    The session printed BR03 KOLKATA for the index and BR04 for the relative reference, then 03 for the index turned into a number with SET.

A subscript past the end writes over the next item unless checks are on

Nothing in the table itself stops a subscript of 6 in a five-entry table: the program computes where entry 6 would start and writes there. In bounds.cob the table of five 6-byte cities is followed in the same group by NEXT-FIELD:

bounds.cob: a write to entry 6 of 5 COB · bounds.cob
       IDENTIFICATION DIVISION.
       PROGRAM-ID. BOUNDS.
      * A table of five cities and the field stored right after it.
      * The subscript 6 is one past the last entry.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  BRANCH-AREA.
           05  BRANCH-TABLE.
               10  BR-CITY  PIC X(6) OCCURS 5 TIMES.
           05  NEXT-FIELD   PIC X(9) VALUE "UNTOUCHED".
       01  SUB          PIC 99.
       PROCEDURE DIVISION.
           MOVE SPACES TO BRANCH-TABLE
           DISPLAY "before: NEXT-FIELD [" NEXT-FIELD "]"
           MOVE 6 TO SUB
           MOVE "MUMBAI" TO BR-CITY(SUB)
           DISPLAY "after BR-CITY(6): NEXT-FIELD [" NEXT-FIELD "]"
           STOP RUN.
A normal build and a -debug build Python · bounds_session.py
from session import files, run

# The same write to entry 6 of a 5-entry table, in a normal build and in a -debug build.
files("bounds.cob")
run("""
cobc -x bounds.cob && ./bounds
!cobc -x -debug -o bounds-debug bounds.cob && ./bounds-debug
""")

Output

$ cobc -x bounds.cob && ./bounds
before: NEXT-FIELD [UNTOUCHED]
after BR-CITY(6): NEXT-FIELD [MUMBAIHED]
$ cobc -x -debug -o bounds-debug bounds.cob && ./bounds-debug
before: NEXT-FIELD [UNTOUCHED]
libcob: bounds.cob:16: error: subscript of 'BR-CITY' out of bounds: 6
note: maximum subscript for 'BR-CITY': 5

 Last statement of "BOUNDS" was MOVE at line 16 of bounds.cob
	ENTRY BOUNDS at bounds.cob:13
 Started by ./bounds-debug
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bounds_session.py

The normal build wrote MUMBAI into the first six bytes of NEXT-FIELD, which then read MUMBAIHED, and ended with status 0. Step through where entry 6 lands:

Interactive · step by step Where entry 6 of a five-entry table lands Step down the record. Each box is one item of BRANCH-AREA, with the bytes it occupies.
Where entry 6 of a five-entry table lands, step 5 of 5. Showing BR-CITY(1), BR-CITY(2), BR-CITY(3) to (4), BR-CITY(5), NEXT-FIELD (bytes 31-39: UNTOUCHED), MOVE to BR-CITY(6). In focus: MOVE to BR-CITY(6).writes hereBR-CITY(1)bytes 1-6BR-CITY(2)bytes 7-12BR-CITY(3) to (4)bytes 13-24BR-CITY(5)bytes 25-30NEXT-FIELDbytes 31-39: UNTOUCHEDMOVE to BR-CITY(6)subscript 6

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Five entries. BR-CITY OCCURS 5 TIMES, 6 bytes each, so the table is bytes 1 to 30 of BRANCH-AREA. Entry n starts at byte 6 x (n - 1) + 1.
  2. The field after the table. NEXT-FIELD follows the table in the same group, at bytes 31 to 39, and holds UNTOUCHED.
  3. Entry 6. The same formula puts entry 6 at byte 31, which is NEXT-FIELD. Without checks, nothing compares the subscript with the 5 of OCCURS.
  4. The overwrite. MOVE "MUMBAI" TO BR-CITY(6) writes six bytes at byte 31, and NEXT-FIELD now reads MUMBAIHED. The normal build ended with status 0, so nothing showed the damage.
  5. With the checks. The -debug build stopped at the MOVE with subscript of 'BR-CITY' out of bounds: 6 and the note that the maximum is 5, so NEXT-FIELD was never touched.

The -debug build stopped at the MOVE with subscript of 'BR-CITY' out of bounds: 6 and the note that the maximum is 5, because -debug switches on the subscript check that GnuCOBOL’s NEWS ties to -fec=bound-subscript. IBM’s compiler has the SSRANGE option for the same purpose, which checks that subscripts and indexes do not “reference areas outside the region of their associated tables”. Its documented default is NOSSRANGE, so a production build without it behaves like the normal build here.

Trusting the data to fit

Wrong: MOVE BRANCH-REC TO BR-ENTRY(BR-COUNT) after ADD 1 TO BR-COUNT, with no test of BR-COUNT. Right: compare the count with the table’s size before every store, as loadfile.cob did, and run the tests with -debug.

Not every out-of-range write is so easy to see. A write past the end changes whatever follows the table, a counter, a total or a flag, and the program goes on with that wrong value; the failure often appears far from the bad subscript.

Key idea

A subscript outside the table is not an error unless a check is compiled in: test counts against the OCCURS limit in the code, and run tests with -debug, or SSRANGE on IBM’s compiler, so a missed case stops the run.

Checkpoint

Check: Out-of-range subscripts and run-time checks

  1. One question In a normal build, MOVE "MUMBAI" TO BR-CITY(6) runs on a table of five 6-byte cities followed by NEXT-FIELD, which holds UNTOUCHED. What happens?

    Choose one answer.

    Show the answer: One question

    Answer: NEXT-FIELD becomes MUMBAIHED, and the run ends normally

    The damage lands on whatever follows the table. With -debug the run stopped at the MOVE, and IBM's SSRANGE option does the same check.

Practise: tables and their limits

The questions below are about the tables, subscripts and indexes of this lesson’s programs. Work out the entry, the byte or the result first, then compare with the session that ran it:

Practice that adapts to you

Practise this lesson

15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 15 What is the subscript of the first entry of a COBOL table?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: 1

    branches.cob read its five entries with SUB from 1 to 5.

  2. Question 2 of 15 Why can BRANCH-TABLE not be written as 01 BR-ENTRY PIC X(12) OCCURS 5 TIMES?

    Choose one answer.

    Show the answer: Question 2 of 15

    Answer: OCCURS is not allowed at level 01, so a table must sit inside a group

    Put the table under an 01 group: 01 BRANCH-TABLE, then 05 BR-ENTRY OCCURS 5 TIMES.

  3. Question 3 of 15 A table has OCCURS 20 TIMES of an entry with a 4-byte code and an 8-byte city. How many bytes is the table?

    Type a number.

    Show the answer: Question 3 of 15

    Answer: 240 bytes

    Each entry is 12 bytes and the entries follow one another, so 20 entries take 240 bytes, as five took 60 in branches.cob.

  4. Question 4 of 15 SALES-TABLE has 2 rows of 3 amounts PIC 9(5)V99. How many bytes does it take?

    Type a number.

    Show the answer: Question 4 of 15

    Answer: 42 bytes

    Each amount is 7 bytes, since V takes no byte, a row is 21 and two rows are 42, as FUNCTION LENGTH printed.

  5. Question 5 of 15 What did GnuCOBOL 3.2 do with COMPUTE TOTAL = FUNCTION SUM(AMT(ALL))?

    Choose one answer.

    Show the answer: Question 5 of 15

    Answer: It refused to compile it, with syntax error, unexpected ALL

    A PERFORM VARYING loop that adds each entry to an accumulator works with both compilers.

  6. Question 6 of 15 In storage, which entry follows MONTH-AMT(1, 3)?

    Choose one answer.

    Show the answer: Question 6 of 15

    Answer: MONTH-AMT(2, 1)

    That is why MONTH-AMT(1, 4) printed 2125.00, the amount of MONTH-AMT(2, 1), in a normal build: a subscript can be wrong while the address is still inside the table.

  7. Question 7 of 15 Why load a list of branches from a file rather than from VALUE clauses in the program?

    Choose one answer.

    Show the answer: Question 7 of 15

    Answer: A new branch is then a new line of data, with no change to the program

    Days of the week can stay as literals; a list of branches, rates or codes that the business changes belongs in a file.

  8. Question 8 of 15 A loader does ADD 1 TO BR-COUNT, then MOVE BRANCH-REC TO BR-ENTRY(BR-COUNT), with no test. What happens on the sixth line of a file for a five-entry table?

    Choose one answer.

    Show the answer: Question 8 of 15

    Answer: The sixth branch is written past the table, over whatever follows it

    The fix is the comparison loadfile.cob makes before each store, and a failed run with a reason.

  9. Question 9 of 15 The branch file will grow from 18 to about 30 branches next year. How should the loader change?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: Raise the OCCURS limit with room to spare and keep the capacity check that fails the run

    Size and check together: a size with room for growth, and a check that fails loudly when even that is exceeded.

  10. Question 10 of 15 Which statements does IBM's guide name for changing an index?

    Choose one answer.

    Show the answer: Question 10 of 15

    Answer: SET, PERFORM VARYING and SEARCH

    indexes.cob used SET BR-IDX TO 1, SET BR-IDX UP BY 2 and PERFORM VARYING BR-IDX.

  11. Question 11 of 15 On IBM's compiler, an index into a table of 12-byte entries points at entry 4. What value does the index hold, by IBM's formula?

    Type a number.

    Show the answer: Question 11 of 15

    Answer: 36 bytes

    IBM's guide gives the index value as the occurrence number minus 1, times the entry length: (4 - 1) x 12 = 36. SET ENTRY-NO TO the index turns it back into 4.

  12. Question 12 of 15 A table will be searched with SEARCH in the next lesson. What must its OCCURS clause have?

    Choose one answer.

    Show the answer: Question 12 of 15

    Answer: An INDEXED BY phrase, because SEARCH works through an index

    That is one reason legacy tables declare INDEXED BY even where the code otherwise uses subscripts.

  13. Question 13 of 15 Which GnuCOBOL build stopped the write to BR-CITY(6)?

    Choose one answer.

    Show the answer: Question 13 of 15

    Answer: The build with -debug

    GnuCOBOL's NEWS ties the check to -fec=bound-subscript, which -debug implies.

  14. Question 14 of 15 What is the default of IBM's SSRANGE option, and what does that mean for production code?

    Choose one answer.

    Show the answer: Question 14 of 15

    Answer: NOSSRANGE, so out-of-range subscripts are not checked unless the build asks for it

    A production build that leaves the default behaves like the normal GnuCOBOL build: the code's own checks are what protect it.

  15. Question 15 of 15 A total printed at the end of a run is wrong, and the code that computes it looks correct. Why suspect a table write elsewhere?

    Choose one answer.

    Show the answer: Question 15 of 15

    Answer: An out-of-range write changes whatever follows the table, such as a total, without an error at the write

    Run the test data with -debug: the bad subscript then stops the run at the statement that writes past the table.

Then extend the loader where GnuCOBOL is installed:

A branch lookup with an error line

Extend loadfile.cob to read a second file of transactions, each with a branch code, and print each transaction’s city by finding its branch in the table with a PERFORM VARYING loop. A code that is not in the table must print an error line, and a branch file of 20 codes must load into a table of 20 entries without touching anything after it. Build it with cobc -x -debug loadfile.cob while you test.

Interview questions

Warm-up (fresher to 2–5 years): how does a subscript differ from an index in COBOL?

A subscript is an ordinary whole-number data item or a literal that gives the entry number, from 1, and you change it with MOVE, ADD or COMPUTE. An index is declared with the table, OCCURS … INDEXED BY, and is changed only with SET, PERFORM VARYING or SEARCH. On IBM’s compiler an index holds a displacement, the entry number minus 1 times the entry’s length, while GnuCOBOL keeps the entry number, so code should rely on neither. SET converts between the two: SET BR-IDX TO 1 starts at the first entry, and SET ENTRY-NO TO BR-IDX gives the entry number for display. SEARCH and SEARCH ALL work only through an index.

Warm-up (fresher to 2–5 years): how many bytes does a two-dimensional table of 2 rows of 3 amounts PIC 9(5)V99 take?

42 bytes. Each amount is PIC 9(5)V99 in USAGE DISPLAY, seven digits and no byte for the implied decimal point, so 7 bytes. A row holds 3 amounts, 21 bytes, and the table holds 2 rows, 42 bytes, stored one row after the other with the inner subscript, the month, varying fastest. FUNCTION LENGTH printed 42 for the lesson’s SALES-TABLE. The same arithmetic gives every entry’s position: MONTH-AMT(B, M) starts at byte 21 x (B - 1) + 7 x (M - 1) + 1, so (2, 3) starts at byte 36.

Two harder questions follow: a table that started to corrupt a total, and whether to switch range checks on in production. Decide your first step before you open each one:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A program keeps 20 branch codes in a table that it loads from a file. Since the file grew to 23 lines, the run's grand total is wrong, although the code that adds it has not changed and the job ends normally. What happened, how do you prove it, and what do you change?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    Production COBOL programs at your site are built without subscript range checks. After two incidents of overwritten storage, someone proposes switching on IBM's SSRANGE, or GnuCOBOL's checks, for every build. How do you decide, and what would you do as well?

    Model answer, Pro. First step and follow-ups

Organisations named on this page

MySmartCoPilot is not linked with IBM or the GnuCOBOL project. OCCURS, subscripting, indexing and SSRANGE are cited from IBM’s manuals, the subscript check from GnuCOBOL’s NEWS, and the outputs come from GnuCOBOL 3.2 runs.

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. From which number does a COBOL subscript count?

    From 1: BR-CODE(1) is the first entry. A subscript may be a literal or a numeric data item, and entry n starts n - 1 entry lengths after the table's first byte.

  2. At which levels is OCCURS not allowed?

    01, 66, 77 and 88, so a table is always part of a group.

  3. In MONTH-AMT(2, 3), which subscript is which?

    The outer table's comes first: branch 2, month 3. Rows are stored one after another, so the inner subscript varies fastest in storage.

  4. Does GnuCOBOL 3.2 accept FUNCTION SUM(AMT(ALL))?

    No, it stops with "syntax error, unexpected ALL", although IBM's compiler takes ALL subscripts. Total a table with a PERFORM VARYING loop and an accumulator.

  5. What should a program check while it loads a table from a file?

    The count of entries stored, against the OCCURS limit, before each store. A file that outgrows the table then fails the job with a reason instead of writing past the end.

  6. How is an index declared, changed and turned into a number?

    Declared with INDEXED BY on the OCCURS, changed only with SET, PERFORM VARYING or SEARCH, and turned into an entry number with SET ENTRY-NO TO BR-IDX.

  7. What does an index hold on IBM's compiler, and on GnuCOBOL?

    On IBM's compiler a displacement, the entry number minus 1 times the entry length; GnuCOBOL keeps the entry number. Code should rely on neither.

  8. What happens when a program writes entry 6 of a five-entry table?

    In a normal build it overwrites whatever follows the table, and the run ends normally. -debug stops it with the subscript out of bounds, and SSRANGE does the same on IBM's compiler, whose default is NOSSRANGE.

Key takeaways

  • OCCURS describes a run of entries with the same layout, numbered from 1, and BR-CODE(3) is computed from the entry size: the five 12-byte branches made a 60-byte table.
  • A table inside a table takes one subscript per level, outer first, MONTH-AMT(2, 3); GnuCOBOL 3.2 refuses IBM’s ALL subscript, so totals need a loop.
  • Values that change belong in a file read at start-up, with a count checked against the OCCURS limit before every store, as the file of six branches showed.
  • A subscript is a number you MOVE and ADD; an index is changed with SET, VARYING or SEARCH, holds a displacement on IBM’s compiler and is turned into a number with SET.
  • Entry 6 of a five-entry table overwrote the next field without an error; -debug, or SSRANGE on IBM’s compiler, stops the run instead.

Terms in this lesson

index
A name declared with INDEXED BY on a table and changed with SET, PERFORM VARYING or SEARCH. On IBM's compiler it holds a displacement, (entry number - 1) times the entry length.
OCCURS clause
The clause that makes an item a table: ITEM OCCURS n TIMES describes n entries of the same layout, one after another, numbered from 1. It is not allowed at levels 01, 66, 77 or 88.
subscript
A number in brackets after a table item that picks one entry, counting from 1, as in BR-CODE(3). It is a literal or a whole-number data item, optionally plus or minus a constant.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.