Pointers and Dynamic Storage

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023), GnuCOBOL 3.2 and IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), and was checked on 2026-10-20. Data pointers, ADDRESS OF, BASED items and ALLOCATE/FREE come from the COBOL 2002 standard, and function pointers and dynamic-length items are COBOL 2014 features. The mainframe-only features (POINTER-32, LP(64), DYNAMIC LENGTH) arrived in Enterprise COBOL 6.3 and are labelled "z/OS; not run here" because GnuCOBOL 3.2 does not implement them; the page shows the real cobc message for each. Every other example was compiled with cobc -x -Wall in GnuCOBOL 3.2.0 (with -std=ibm where it says so) without warnings, run, and the output shown is real.

COBOL was designed so that a programmer rarely needs an address: data lives in fixed places that the compiler names. Pointers exist for the cases where that is not enough: storage whose size is known only at run time, a chain of records that grow and shrink, a buffer returned by a C function, a program chosen by name while running, or a record area handed over by CICS or IMS. The page that follows covers the data-pointer side first (POINTER, ADDRESS OF, BASED, ALLOCATE, FREE), then program pointers, the 64-bit variants and dynamic-length items. It builds on Usage and Numeric Representation (the USAGE clause), Data Division and Level Numbers (LINKAGE SECTION) and Tables and OCCURS (the fixed-size alternative), and it is continued in Subprograms: CALL and Linkage and Interoperability with C, Java and Python.

What a pointer is

A pointer data item contains a storage address and nothing else. It has no PICTURE, belongs to no class or category, is not numeric and is not a conditional variable. You declare it with USAGE POINTER (the word USAGE IS is optional), and you give it its initial value with VALUE NULL, because NULL is the only value a VALUE clause may give a pointer. NULL (or NULLS) is a figurative constant that means "an invalid address, pointing nowhere".

Operation Statement Notes

Give a pointer a starting value

VALUE NULL in the entry, or SET ptr TO NULL

NULL can be the sending field of SET, never the receiving one.

Copy a pointer

SET ptr-2 TO ptr-1

SET is the form to use. Both compilers also accept a pointer-to-pointer MOVE, but a number cannot be moved to a pointer (MOVE 0 TO ptr is rejected, see below).

Compare two pointers

IF ptr-1 = ptr-2, IF ptr = NULL

Only equality and inequality make sense; there is no ordering.

Take the address of a data item

SET ptr TO ADDRESS OF item

item may be in WORKING-STORAGE, LOCAL-STORAGE or LINKAGE; not level 66 or 88.

Point a LINKAGE item at storage

SET ADDRESS OF item TO ptr

item must be a level 01 or 77 LINKAGE item.

Get dynamic storage

ALLOCATE, released with FREE

Below.

Pass an address to another program

CALL "x" USING BY REFERENCE ptr or ... ADDRESS OF item

The callee declares the parameter as USAGE POINTER in its LINKAGE SECTION.

Enterprise COBOL lists the places a pointer may appear: ALLOCATE, FREE, format 5 of SET, a relation condition, and the USING phrase of CALL, ENTRY and the PROCEDURE DIVISION header. A pointer can have GLOBAL, EXTERNAL, OCCURS and VOLATILE, but never JUSTIFIED, PICTURE or BLANK WHEN ZERO, and it is ignored by CORRESPONDING. It can be written to a file, but the address read back later is not meaningful, so never store pointers in records.

A pointer is as wide as an address. On the 64-bit GnuCOBOL build used here LENGTH OF a pointer is 8 bytes; in Enterprise COBOL it is 4 bytes with LP(32) (the default) and 8 bytes with LP(64), which is why a copybook that holds pointers can change size between compilations (see POINTER-32 and AMODE 64).

NULL, comparison and the size of a pointer

The program below shows the basic life cycle of a pointer: a VALUE NULL start, the size, equality between two pointers, FREE resetting a pointer, and what happens on a second FREE and on an allocation of zero bytes. ALLOCATE and FREE themselves are explained in the next section.

      * Circulation: pointer size, NULL and repeated FREE
       IDENTIFICATION DIVISION.
       PROGRAM-ID. NULL1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       77  WS-PTR                  USAGE POINTER VALUE NULL.
       77  WS-COPY                 USAGE POINTER VALUE NULL.
       PROCEDURE DIVISION.
       MAIN-PARA.
           DISPLAY "Pointer size: " LENGTH OF WS-PTR " bytes"
           IF WS-PTR = NULL
               DISPLAY "VALUE NULL: not pointing anywhere"
           END-IF
           ALLOCATE 100 CHARACTERS RETURNING WS-PTR
           SET WS-COPY TO WS-PTR
           IF WS-COPY = WS-PTR
               DISPLAY "Two pointers hold the same address"
           END-IF
           FREE WS-PTR
           IF WS-PTR = NULL
               DISPLAY "FREE reset WS-PTR to NULL"
           END-IF
           IF WS-COPY NOT = NULL
               DISPLAY "WS-COPY still holds the old address"
           END-IF
           FREE WS-PTR
           DISPLAY "A second FREE of a NULL pointer did nothing"
           ALLOCATE 0 CHARACTERS RETURNING WS-PTR
           IF WS-PTR = NULL
               DISPLAY "ALLOCATE of zero bytes gave NULL"
           END-IF
           STOP RUN.
Pointer size: 8 bytes
VALUE NULL: not pointing anywhere
Two pointers hold the same address
FREE reset WS-PTR to NULL
WS-COPY still holds the old address
A second FREE of a NULL pointer did nothing
ALLOCATE of zero bytes gave NULL

WS-COPY shows the main hazard of pointers: FREE resets only the pointer it is given. Every other copy of the address now points at released storage (a dangling pointer) and nothing in the language warns you. Enterprise COBOL specifies the same two results: FREE sets the pointer to NULL, and an ALLOCATE whose size is 0 or negative sets the receiving pointer to NULL. The GnuCOBOL manual says the size must be a non-zero positive integer and does not mention the reset; the 3.2.0 runtime behaves like IBM here, so test for NULL rather than relying on either text.

ADDRESS OF and SET ADDRESS OF

ADDRESS OF is a special register, implicitly USAGE POINTER, that exists for every data item. It has two roles:

  • As a sending operand it gives the address of any item in WORKING-STORAGE, LOCAL-STORAGE or the LINKAGE SECTION: SET ptr TO ADDRESS OF item, or CALL "x" USING BY REFERENCE ADDRESS OF item. A function-identifier is not allowed as its operand.

  • As a receiving operand, only for a level 01 or 77 LINKAGE SECTION item: SET ADDRESS OF item TO ptr. This is how a LINKAGE item is based, that is, made to describe the bytes at an address. The item must not be reference-modified.

Because a LINKAGE SECTION item allocates no storage, its description is a template that you can lay over any address. After SET ADDRESS OF BOOK-RECORD TO ptr, a reference to BOOK-ID reads the first six bytes at the address in ptr. The special register ADDRESS OF item can also be tested: for an item that has not been based yet, or has been released with FREE, it compares equal to NULL, which is the reliable way to ask whether the item is currently addressable.

Passing an address to a subprogram

The main program below puts a book record in WORKING-STORAGE and calls a subprogram twice to add one copy: once passing a pointer variable and once passing ADDRESS OF directly. In both cases the callee receives a USAGE POINTER parameter and bases its own description of the record on it, so it changes the caller’s bytes, not a copy. The record is the BOOKREC copybook of the Circulation scenario (see Copybooks and REPLACE).

      * Circulation: pass the address of a record to a subprogram
       IDENTIFICATION DIVISION.
       PROGRAM-ID. ADDR1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
           COPY BOOKREC.
       77  WS-BOOK-PTR             USAGE POINTER.
       PROCEDURE DIVISION.
       MAIN-PARA.
           MOVE 2 TO BOOK-ID
           MOVE 2 TO BOOK-COPIES
           SET WS-BOOK-PTR TO ADDRESS OF BOOK-RECORD
           CALL "ADDCOPY" USING BY REFERENCE WS-BOOK-PTR
           CALL "ADDCOPY" USING BY REFERENCE ADDRESS OF BOOK-RECORD
           DISPLAY "Book " BOOK-ID " now has " BOOK-COPIES " copies"
           STOP RUN.
       END PROGRAM ADDR1.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ADDCOPY.
       DATA DIVISION.
       LINKAGE SECTION.
       77  LS-PTR                  USAGE POINTER.
       01  LS-BOOK.
           05  LS-BOOK-ID          PIC 9(6).
           05  FILLER              PIC X(13).
           05  FILLER              PIC X(30).
           05  LS-COPIES           PIC 9(3).
           05  FILLER              PIC X(8).
       PROCEDURE DIVISION USING LS-PTR.
           SET ADDRESS OF LS-BOOK TO LS-PTR
           ADD 1 TO LS-COPIES
           GOBACK.
       END PROGRAM ADDCOPY.
Book 000002 now has 004 copies

The subprogram describes only the fields it needs, with FILLER for the rest, and the sizes must add up to the real record (6 + 13 + 30 + 3 + 8 = 60 bytes). Nothing checks that the caller’s record really has that layout; getting it from a shared copybook is the safe way. The same program compiles and gives the same result with -std=ibm. Passing parameters is covered in Subprograms: CALL and Linkage.

Dynamic storage: ALLOCATE and FREE

Everything so far used storage that the compiler had already reserved. ALLOCATE asks the run-time system for more, and FREE gives it back. The ALLOCATE statement has two forms:

  • By size: ALLOCATE expression CHARACTERS [INITIALIZED] RETURNING ptr. The expression is a number of bytes. The address of the new block goes into ptr. With INITIALIZED the block is filled with binary zeros; without it the contents are whatever the allocator left.

  • By item: ALLOCATE item [INITIALIZED] [RETURNING ptr]. Storage the size of item is obtained and item is based on it, as if you had written SET ADDRESS OF item TO the new address. With INITIALIZED, the item is initialised as INITIALIZE item WITH FILLER ALL TO VALUE THEN TO DEFAULT would do, so VALUE clauses take effect. RETURNING is optional here and, if given, also stores the address.

FREE ptr releases the block that ptr points to. If the allocation could not be satisfied, the receiving pointer (or the based item’s address) is set to NULL, so check it.

The two compilers differ about which items the by-item form accepts, and this is the main portability point of the page.

Aspect Enterprise COBOL 6.5 GnuCOBOL 3.2

ALLOCATE item target

A level 01 or 77 LINKAGE SECTION item. There is no BASED clause.

A level 01 or 77 item with the BASED clause (WORKING-STORAGE, LOCAL-STORAGE or LINKAGE). A plain LINKAGE item is accepted only with -std=ibm.

FREE operand

A POINTER or POINTER-32 item only.

A pointer, or a BASED item (then it becomes un-based).

Where the storage comes from

The Language Environment heap; the optional LOC 24, LOC 31 or LOC 64 phrase and the DATA and LP options choose the area.

The run-time library’s allocator. The LOC phrase is accepted but ignored (warning: ignoring LOC phrase).

Lifetime

Until FREE or the end of the run unit; it is not released when the program that obtained it returns.

Release it with FREE as well; do not rely on the end of the program to do it.

IBM also uses ALLOCATE and FREE to resize an OCCURS 1 TO UNBOUNDED table (see Tables and OCCURS).

Allocating one record (IBM-compatible form)

The first program obtains one book record’s worth of storage, reads the first line of BOOKMAST into it, then frees it. It uses the form that works in both products: a LINKAGE SECTION item from the copybook, ALLOCATE by item with RETURNING, and FREE on the pointer. It needs -std=ibm in GnuCOBOL.

      * Circulation: obtain one book record in dynamic storage
       IDENTIFICATION DIVISION.
       PROGRAM-ID. ALLOC1.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT BOOKMAST ASSIGN TO "BOOKMAST"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  BOOKMAST.
       01  BOOK-IN                 PIC X(60).
       WORKING-STORAGE SECTION.
       77  WS-BOOK-PTR             USAGE POINTER VALUE NULL.
       LINKAGE SECTION.
           COPY BOOKREC.
       PROCEDURE DIVISION.
       MAIN-PARA.
           ALLOCATE BOOK-RECORD RETURNING WS-BOOK-PTR
           IF WS-BOOK-PTR = NULL
               DISPLAY "No storage"
               STOP RUN
           END-IF
           OPEN INPUT BOOKMAST
           READ BOOKMAST INTO BOOK-RECORD
               AT END DISPLAY "Empty file"
           END-READ
           CLOSE BOOKMAST
           DISPLAY "Allocated " LENGTH OF BOOK-RECORD " bytes"
           DISPLAY BOOK-ID " " BOOK-TITLE
           FREE WS-BOOK-PTR
           IF WS-BOOK-PTR = NULL
               DISPLAY "Pointer is NULL after FREE"
           END-IF
           STOP RUN.
Allocated 60 bytes
000001 DATA STRUCTURES
Pointer is NULL after FREE

Compiled with -std=ibm. Without it, GnuCOBOL 3.2.0 refuses the ALLOCATE although its manual says a LINKAGE item is allowed (the manual adds that it does not recommend this). This is the real message:

alloc1.cob:19: error: target of ALLOCATE must have BASED clause
   17 |        PROCEDURE DIVISION.
   18 |        MAIN-PARA.
   19 >            ALLOCATE BOOK-RECORD RETURNING WS-BOOK-PTR
   20 |            IF WS-BOOK-PTR = NULL
   21 |                DISPLAY "No storage"

The size form, ALLOCATE n CHARACTERS RETURNING ptr, followed by SET ADDRESS OF, compiles in every dialect, and is the form to choose when the code must build on both compilers without options.

BASED items (GnuCOBOL and the 2002 standard)

A BASED item is a level 01 or 77 data item that has no storage until you give it some, either with ALLOCATE or with SET ADDRESS OF. The BASED clause is in the standard and in GnuCOBOL; Enterprise COBOL has no BASED and uses LINKAGE items for the same purpose. BASED is valid in WORKING-STORAGE, LOCAL-STORAGE and LINKAGE, only on levels 01 and 77, and cannot be combined with ANY LENGTH. The next program allocates a scratch group for a fine calculation, uses it, frees it, and shows that the item is un-based afterwards.

      * Circulation: a BASED work area, allocated and freed on demand
       IDENTIFICATION DIVISION.
       PROGRAM-ID. BASED1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  FINE-WORK BASED.
           05  FW-MEMBER-ID        PIC 9(6).
           05  FW-DAYS-LATE        PIC 9(3).
           05  FW-AMOUNT           PIC 9(3)V99.
       77  WS-FINE-PTR             USAGE POINTER.
       PROCEDURE DIVISION.
       MAIN-PARA.
           ALLOCATE FINE-WORK INITIALIZED RETURNING WS-FINE-PTR
           DISPLAY "Allocated " LENGTH OF FINE-WORK " bytes, amount "
               FW-AMOUNT
           MOVE 101 TO FW-MEMBER-ID
           MOVE 14 TO FW-DAYS-LATE
           COMPUTE FW-AMOUNT = FW-DAYS-LATE * 0.25
           DISPLAY FW-MEMBER-ID " owes " FW-AMOUNT
           FREE FINE-WORK
           IF ADDRESS OF FINE-WORK = NULL
               DISPLAY "FINE-WORK is no longer based"
           END-IF
           STOP RUN.
Allocated 14 bytes, amount 000.00
000101 owes 003.50
FINE-WORK is no longer based

INITIALIZED is what makes the amount start at zero; without it the bytes would be undefined. FREE FINE-WORK takes the item itself because it is BASED, and afterwards ADDRESS OF FINE-WORK is NULL. The arithmetic itself is the subject of Arithmetic and COMPUTE.

Storage whose size is decided at run time

A fixed OCCURS 1000 TIMES table wastes space when 4 records are loaded and fails when 1001 arrive. With a pointer you can size the block from a count. The program below allocates exactly four book records, bases a LINKAGE table on the block, loads BOOKMAST into it with a subscript and prints it. The OCCURS 1000 is only a description (a template that no storage backs); the real limit is the number of bytes obtained.

      * Circulation: load BOOKMAST into dynamically sized storage
       IDENTIFICATION DIVISION.
       PROGRAM-ID. WALK1.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT BOOKMAST ASSIGN TO "BOOKMAST"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  BOOKMAST.
           COPY BOOKREC.
       WORKING-STORAGE SECTION.
       77  WS-TABLE-PTR            USAGE POINTER VALUE NULL.
       77  WS-BYTES                PIC 9(9) BINARY.
       77  WS-COUNT                PIC 9(4) BINARY VALUE 0.
       77  WS-EOF                  PIC X VALUE "N".
           88  AT-END-OF-FILE      VALUE "Y".
       77  WS-IX                   PIC 9(4) BINARY.
       LINKAGE SECTION.
       01  BOOK-TABLE.
           05  BOOK-ENTRY          OCCURS 1000 TIMES.
               10  BT-ID           PIC 9(6).
               10  BT-ISBN         PIC X(13).
               10  BT-TITLE        PIC X(30).
               10  BT-COPIES       PIC 9(3).
               10  BT-PRICE        PIC 9(4)V99.
               10  BT-STATUS       PIC X.
               10  FILLER          PIC X(1).
       PROCEDURE DIVISION.
       MAIN-PARA.
           COMPUTE WS-BYTES = 4 * LENGTH OF BOOK-RECORD
           ALLOCATE WS-BYTES CHARACTERS INITIALIZED
               RETURNING WS-TABLE-PTR
           SET ADDRESS OF BOOK-TABLE TO WS-TABLE-PTR
           OPEN INPUT BOOKMAST
           PERFORM UNTIL AT-END-OF-FILE
               READ BOOKMAST
                   AT END SET AT-END-OF-FILE TO TRUE
                   NOT AT END
                       ADD 1 TO WS-COUNT
                       MOVE BOOK-RECORD TO BOOK-ENTRY (WS-COUNT)
               END-READ
           END-PERFORM
           CLOSE BOOKMAST
           PERFORM VARYING WS-IX FROM 1 BY 1 UNTIL WS-IX > WS-COUNT
               DISPLAY BT-ID (WS-IX) " " BT-TITLE (WS-IX)
                   " " BT-STATUS (WS-IX)
           END-PERFORM
           DISPLAY WS-COUNT " books in " WS-BYTES " bytes"
           FREE WS-TABLE-PTR
           STOP RUN.
000001 DATA STRUCTURES                A
000002 COBOL BASICS                   A
000003 BATCH PROCESSING               A
000004 OLD LEDGERS                    W
0004 books in 000000240 bytes

The program trusts WS-COUNT to stay within 4. A fifth record would be written past the allocated block and overwrite whatever lies there, and the compiler cannot know, because the table says 1000. If the number of records is not known in advance, count first (or read the file twice), or grow the block as shown in IBM’s unbounded-table example. Subscript checking is described in Tables and OCCURS and does not help here: the subscript is within the declared 1000.

Pointer arithmetic: GnuCOBOL only

COBOL has no pointer arithmetic in the standard sense, and ADD 60 TO WS-PTR is an error (see the diagnostics below). GnuCOBOL adds SET ptr UP BY n and SET ptr DOWN BY n, counted in bytes, which lets a program walk a block record by record without a table. Enterprise COBOL’s format 5 of SET has no UP BY; IBM’s Programming Guide says that to skip a header you base a second LINKAGE item on ADDRESS OF a field inside the first one (SET ADDRESS OF SALARY-REC TO ADDRESS OF REAL-SALARY-REC), which also works in GnuCOBOL.

      * Circulation: walk a block of book records with pointer arithmetic
       IDENTIFICATION DIVISION.
       PROGRAM-ID. UPBY1.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT BOOKMAST ASSIGN TO "BOOKMAST"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  BOOKMAST.
       01  BOOK-IN                 PIC X(60).
       WORKING-STORAGE SECTION.
       77  WS-BLOCK-PTR            USAGE POINTER VALUE NULL.
       77  WS-CURSOR               USAGE POINTER VALUE NULL.
       77  WS-COUNT                PIC 9(4) BINARY VALUE 0.
       77  WS-IX                   PIC 9(4) BINARY.
       77  WS-EOF                  PIC X VALUE "N".
           88  AT-END-OF-FILE      VALUE "Y".
       LINKAGE SECTION.
           COPY BOOKREC.
       PROCEDURE DIVISION.
       MAIN-PARA.
           ALLOCATE 240 CHARACTERS INITIALIZED RETURNING WS-BLOCK-PTR
           SET WS-CURSOR TO WS-BLOCK-PTR
           OPEN INPUT BOOKMAST
           PERFORM UNTIL AT-END-OF-FILE
               READ BOOKMAST
                   AT END SET AT-END-OF-FILE TO TRUE
                   NOT AT END
                       SET ADDRESS OF BOOK-RECORD TO WS-CURSOR
                       MOVE BOOK-IN TO BOOK-RECORD
                       SET WS-CURSOR UP BY LENGTH OF BOOK-RECORD
                       ADD 1 TO WS-COUNT
               END-READ
           END-PERFORM
           CLOSE BOOKMAST
           SET WS-CURSOR TO WS-BLOCK-PTR
           PERFORM VARYING WS-IX FROM 1 BY 1 UNTIL WS-IX > WS-COUNT
               SET ADDRESS OF BOOK-RECORD TO WS-CURSOR
               DISPLAY BOOK-ID " " BOOK-COPIES " " BOOK-STATUS
               SET WS-CURSOR UP BY LENGTH OF BOOK-RECORD
           END-PERFORM
           FREE WS-BLOCK-PTR
           STOP RUN.
000001 003 A
000002 002 A
000003 001 A
000004 001 W

Note that cobc -std=ibm still accepts SET ... UP BY on a pointer, so a successful -std=ibm compile does not prove the program is acceptable to Enterprise COBOL: that compiler would reject it. For code that must run on both, use the table form of the previous example.

A linked list

A chained list is the classic reason for pointers: each node holds a pointer to the next, the last node holds NULL, and the list grows one ALLOCATE at a time without any fixed upper limit. IBM’s Programming Guide uses the same pattern, ending the walk when the next pointer is NULL.

flowchart LR H["WS-HEAD"] --> N1["Node 1: member 100, book 1"] N1 -->|NODE-NEXT| N2["Node 2: member 101, book 2"] N2 -->|NODE-NEXT| N3["Node 3: member 102, book 3"] N3 -->|NODE-NEXT| X["NULL"] T["WS-TAIL"] --> N3

The program reads the day’s transactions (LOANTRAN, record LOANREC), appends a node for each checkout (O) at the tail, lists the chain, then frees every node. LOAN-NODE is a LINKAGE item that is re-based on each node in turn; it is only ever a window onto one node at a time. The program needs -std=ibm because it uses ALLOCATE by item on a LINKAGE item (the Enterprise COBOL form).

      * Circulation: chain the day's checkouts in a linked list
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CHAIN1.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOANTRAN ASSIGN TO "LOANTRAN"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOANTRAN.
           COPY LOANREC.
       WORKING-STORAGE SECTION.
       77  WS-HEAD                 USAGE POINTER VALUE NULL.
       77  WS-TAIL                 USAGE POINTER VALUE NULL.
       77  WS-NEW                  USAGE POINTER VALUE NULL.
       77  WS-NEXT                 USAGE POINTER VALUE NULL.
       77  WS-NODES                PIC 9(3) VALUE 0.
       77  WS-EOF                  PIC X VALUE "N".
           88  AT-END-OF-FILE      VALUE "Y".
       LINKAGE SECTION.
       01  LOAN-NODE.
           05  NODE-NEXT           USAGE POINTER.
           05  NODE-MEMBER-ID      PIC 9(6).
           05  NODE-BOOK-ID        PIC 9(6).
       PROCEDURE DIVISION.
       MAIN-PARA.
           OPEN INPUT LOANTRAN
           PERFORM UNTIL AT-END-OF-FILE
               READ LOANTRAN
                   AT END SET AT-END-OF-FILE TO TRUE
                   NOT AT END
                       IF LOAN-CHECKOUT
                           PERFORM APPEND-NODE
                       END-IF
               END-READ
           END-PERFORM
           CLOSE LOANTRAN
           PERFORM LIST-NODES
           PERFORM FREE-NODES
           DISPLAY "Nodes freed: " WS-NODES
           STOP RUN.

       APPEND-NODE.
           ALLOCATE LOAN-NODE INITIALIZED RETURNING WS-NEW
           SET NODE-NEXT TO NULL
           MOVE LOAN-MEMBER-ID TO NODE-MEMBER-ID
           MOVE LOAN-BOOK-ID TO NODE-BOOK-ID
           IF WS-HEAD = NULL
               SET WS-HEAD TO WS-NEW
           ELSE
               SET ADDRESS OF LOAN-NODE TO WS-TAIL
               SET NODE-NEXT TO WS-NEW
           END-IF
           SET WS-TAIL TO WS-NEW
           ADD 1 TO WS-NODES.

       LIST-NODES.
           SET ADDRESS OF LOAN-NODE TO WS-HEAD
           PERFORM UNTIL ADDRESS OF LOAN-NODE = NULL
               DISPLAY "Member " NODE-MEMBER-ID " borrowed "
                   NODE-BOOK-ID
               SET ADDRESS OF LOAN-NODE TO NODE-NEXT
           END-PERFORM.

       FREE-NODES.
           MOVE 0 TO WS-NODES
           SET WS-NEW TO WS-HEAD
           PERFORM UNTIL WS-NEW = NULL
               SET ADDRESS OF LOAN-NODE TO WS-NEW
               SET WS-NEXT TO NODE-NEXT
               FREE WS-NEW
               ADD 1 TO WS-NODES
               SET WS-NEW TO WS-NEXT
           END-PERFORM.
Member 000100 borrowed 000001
Member 000101 borrowed 000002
Member 000102 borrowed 000003
Nodes freed: 003

Three details carry the correctness. First, the NODE-NEXT of a new node is set to NULL explicitly before the node is linked, because the end of the list is recognised by that value. Second, FREE-NODES copies NODE-NEXT into WS-NEXT before FREE, because reading the node afterwards is reading freed storage. Third, the tail pointer makes appending one step instead of a walk of the whole chain. The three return-record transactions (R) are skipped by the IF LOAN-CHECKOUT, so only the three checkouts are chained. A real nightly update would normally use a keyed table or an indexed file instead (see Searching and Sorting Tables); the list is the right tool when order of arrival matters and the volume is unknown.

Pitfalls with real diagnostics

Each program in this section is deliberately wrong. The compile-time errors are the real cobc 3.2.0 text.

Mistake Code Result in GnuCOBOL 3.2.0

BASED on a subordinate item

05 FW-MEMBER-ID PIC 9(6) BASED.

error: BASED only allowed at 01/77 level

Arithmetic on a pointer

ADD 60 TO WS-PTR

error: 'WS-PTR' is not a numeric name

Numeric value into a pointer

MOVE 0 TO WS-PTR

error: invalid MOVE statement (use SET WS-PTR TO NULL)

ALLOCATE of a plain LINKAGE item

ALLOCATE BOOK-RECORD ...

error: target of ALLOCATE must have BASED clause unless -std=ibm

The two run-time mistakes do not stop the compiler at all. A BASED item used before ALLOCATE:

      * Deliberately wrong: BASED item used before ALLOCATE
       IDENTIFICATION DIVISION.
       PROGRAM-ID. BAD1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  FINE-WORK BASED.
           05  FW-MEMBER-ID        PIC 9(6).
       PROCEDURE DIVISION.
       MAIN-PARA.
           MOVE 101 TO FW-MEMBER-ID
           DISPLAY FW-MEMBER-ID
           STOP RUN.

compiles cleanly with cobc -x -Wall, and when run it dies (exit status 139) with:

attempt to reference invalid memory address (signal)

The same happens when a second program uses the item after FREE FINE-WORK (the second wrong example, BAD2). Compiled with -debug, which turns on run-time checks, the failure is diagnosed at the statement instead:

libcob: bad1.cob:10: error: BASED/LINKAGE item 'FINE-WORK' (accessed by 'FW-MEMBER-ID') has NULL address

 Last statement of "BAD1" was MOVE
	MAIN-PARA at bad1.cob:10
	ENTRY BAD1 at bad1.cob:9
 Started by ./bad1.out

The GnuCOBOL manual describes the result of referencing unallocated or freed storage as unpredictable; here it is a crash, but on a different platform, or when the storage has been reused by the allocator, it can be silent corruption instead. Habits that prevent it: test ADDRESS OF item = NULL (or the pointer) before using a based item, set every pointer to NULL explicitly after FREE if copies of it exist, and build test runs with -debug. Storage overlays and invalid addresses on z/OS are found with the techniques covered in Debugging and Troubleshooting.

Procedure pointers and function pointers

A program pointer holds the address of an entry point instead of a data item. Two usages exist, both set with format 6 of SET:

  • USAGE PROCEDURE-POINTER, which can contain the address of the primary entry point of a COBOL program, of an alternate entry point defined by an ENTRY statement, or of an entry point in a non-COBOL program, or NULL. It may be used in a relation condition (equality only), as an operand of CALL that names the program to call, and in the USING phrase of ENTRY and PROCEDURE DIVISION.

  • USAGE FUNCTION-POINTER, the COBOL 2014 usage. It has the same capabilities as a procedure pointer and is the one that interoperates more easily with C function pointers.

SET ptr TO ENTRY literal-or-identifier sets the pointer; SET ptr TO NULL clears it; CALL ptr USING ... calls through it. The target must be the outermost program of a compilation unit (a nested program cannot be the target on z/OS), and the name is affected by the PGMNAME compiler option. The reason to use a program pointer instead of CALL identifier is to look the program up once and call it many times, to hold a table of programs, or to receive a callback address from another language.

In the example below, the main program selects the fine rule by name (adult or children’s rate), resolves it into a procedure pointer, and calls through the pointer. The two rules are separate outermost programs in the same source file.

      * Circulation: choose the fine rule at run time
       IDENTIFICATION DIVISION.
       PROGRAM-ID. PROCPTR1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       77  WS-RULE-PTR             USAGE PROCEDURE-POINTER VALUE NULL.
       77  WS-RULE-NAME            PIC X(8).
       77  WS-DAYS-LATE            PIC 9(3) VALUE 14.
       77  WS-FINE                 PIC 9(3)V99 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           MOVE "FINEADLT" TO WS-RULE-NAME
           PERFORM APPLY-RULE
           MOVE "FINEKIDS" TO WS-RULE-NAME
           PERFORM APPLY-RULE
           SET WS-RULE-PTR TO NULL
           IF WS-RULE-PTR = NULL
               DISPLAY "No rule selected"
           END-IF
           STOP RUN.

       APPLY-RULE.
           SET WS-RULE-PTR TO ENTRY WS-RULE-NAME
           CALL WS-RULE-PTR USING WS-DAYS-LATE WS-FINE
           DISPLAY WS-RULE-NAME " " WS-DAYS-LATE " days: " WS-FINE.
       END PROGRAM PROCPTR1.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FINEADLT.
       DATA DIVISION.
       LINKAGE SECTION.
       77  LS-DAYS                 PIC 9(3).
       77  LS-FINE                 PIC 9(3)V99.
       PROCEDURE DIVISION USING LS-DAYS LS-FINE.
           COMPUTE LS-FINE = LS-DAYS * 0.25
           GOBACK.
       END PROGRAM FINEADLT.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FINEKIDS.
       DATA DIVISION.
       LINKAGE SECTION.
       77  LS-DAYS                 PIC 9(3).
       77  LS-FINE                 PIC 9(3)V99.
       PROCEDURE DIVISION USING LS-DAYS LS-FINE.
           COMPUTE LS-FINE = LS-DAYS * 0.05
           GOBACK.
       END PROGRAM FINEKIDS.
FINEADLT 014 days: 003.50
FINEKIDS 014 days: 000.70
No rule selected

On z/OS the same source needs the programs available at run time under those names (compiled together in one batch compilation as here, or as separate load modules found by the Language Environment search). IBM’s own example makes the same point with SET FP TO ENTRY "subp1" followed by CALL FP.

GnuCOBOL 3.2.0 implements PROCEDURE-POINTER as a synonym of its PROGRAM-POINTER usage, but does not implement FUNCTION-POINTER or, as the next section shows, POINTER-32. This program, which uses both on purpose, produces the real messages:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FUNCPTR1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       77  WS-FP                   USAGE FUNCTION-POINTER.
       77  WS-P32                  USAGE POINTER-32.
       PROCEDURE DIVISION.
           SET WS-FP TO ENTRY "FINEADLT"
           STOP RUN.
funcptr1.cob:5: error: syntax error, unexpected ., expecting intrinsic function name
funcptr1.cob:6: error: unknown USAGE: POINTER-32
funcptr1.cob:8: error: SET targets must be PROGRAM-POINTER

With -std=ibm the second message becomes 'POINTER-32' is a reserved word, but isn't supported. The first message is misleading: cobc reads FUNCTION-POINTER as the start of an intrinsic-function reference and reports a syntax error where it expects a function name. In GnuCOBOL, use PROCEDURE-POINTER (or PROGRAM-POINTER) for the same job; to call a C function through its address, call it by name and let the linker resolve it, as in the C example below.

POINTER-32 and AMODE 64

By default Enterprise COBOL generates AMODE 31 code (LP(32)), in which every address is 4 bytes. Since Enterprise COBOL 6.3 the LP(64) compiler option generates AMODE 64 code that can use storage above the 2 GB bar. The effect on this page’s topics:

Item LP(32) LP(64)

USAGE POINTER

4 bytes

8 bytes

USAGE FUNCTION-POINTER, USAGE INDEX

4 bytes

8 bytes

USAGE POINTER-32

4 bytes (synonym of POINTER)

4 bytes, holds an address below the bar

LENGTH OF special register

PIC 9(9) BINARY

PIC 9(18) BINARY

ALLOCATE default location

below the bar (DATA(31)) or below the line (DATA(24))

above the bar for POINTER, below the bar for POINTER-32

POINTER-32 exists so that a copybook shared between LP(32) and LP(64) programs, or a record layout that a 31-bit program must also read, keeps the same size. The Programming Guide adds that PROCEDURE-POINTER items are 8 bytes whatever the LP setting, unlike FUNCTION-POINTER items. A POINTER can be set from a POINTER-32 (the high-order word is cleared), and the reverse is a programming error if the 64-bit value does not fit in 32 bits. The two can be compared in a relation condition, and they are synonyms under LP(32).

Other consequences of LP(64) that matter to this page: the ALLOCATE LOC 64 phrase is only valid under LP(64), LOC 24 and LOC 31 ask for storage below the line and below the bar, and a diagnostic is issued when the combination is impossible (for example LOC 64 with LP(32)). Language Environment does not support mixing AMODE 64 and AMODE 31 programs in one application, AMODE 64 programs cannot run in CICS or IMS, and an LP(64) program cannot contain GO TO or ALTER, among other restrictions listed with the option. Compiling and binding AMODE 64 programs is a Language Environment and binder topic; see Compiling, Linking and Language Environment.

GnuCOBOL has no POINTER-32. On a 64-bit build its USAGE POINTER is 8 bytes (shown above), on a 32-bit build 4 bytes, and the diagnostics for POINTER-32 are shown in the previous section.

DYNAMIC LENGTH items

A dynamic-length elementary item is a data item whose logical length changes at run time, from zero up to a limit, with storage managed by the run time. It was added by the COBOL 2014 standard and by Enterprise COBOL 6.3 (z/OS; not run here: GnuCOBOL 3.2 has no DYNAMIC LENGTH). It is the COBOL answer to a string that has no sensible fixed size, such as a free-text note on a loan record.

The clause is DYNAMIC [LENGTH] [LIMIT [IS] integer] on an elementary item whose PICTURE is a single X (class alphanumeric) or a single U (class UTF-8). Without LIMIT the maximum is 999999999 characters (bytes for U) or whatever memory permits; characters beyond the limit are truncated on the right at a character boundary.

       01  LOAN-NOTE               PIC X DYNAMIC LENGTH LIMIT 2000.
       01  WS-NOTE-LEN             PIC 9(4) BINARY.
      *    ...
           MOVE "Overdue: please return" TO LOAN-NOTE
           DISPLAY LENGTH OF LOAN-NOTE
           SET LENGTH OF LOAN-NOTE TO 7
           DISPLAY LOAN-NOTE
           MOVE LENGTH OF LOAN-NOTE TO WS-NOTE-LEN

This fragment (z/OS; not run here) shows the rules that follow. After the MOVE, the length of LOAN-NOTE is that of the sender (22), and LENGTH OF LOAN-NOTE is the current byte length, not the limit. SET LENGTH OF LOAN-NOTE TO 7 is format 8 of SET: the receiver must be a dynamic-length elementary item and the sender a numeric integer item or integer literal of zero or more. A value below the current length truncates on the right; a value above it extends the item without padding or initialising the new positions, which are therefore undefined until you move data in. A negative value sets the length to zero.

Summary of the rules in Enterprise COBOL 6.5:

  • As a sending operand the item behaves as a fixed-length item of its current length; as a receiving operand without reference modification the buffer is grown if needed and the length is set to that of the sender (a zero-length sender sets the length to zero). Reference-modified receivers do not allocate or change the length, and they must not be used before the item has been given a value.

  • It may be in the FILE, WORKING-STORAGE, LOCAL-STORAGE or LINKAGE SECTION. In the FILE SECTION it must be level 01 or 77. In LINKAGE it must also be a USING BY REFERENCE parameter or the RETURNING item.

  • Supported statements are ACCEPT (format 2), CALL, CANCEL, DISPLAY, EVALUATE, MOVE, SET, STOP, STRING and UNSTRING.

  • A group that contains a dynamic-length item is a dynamic-length group. It cannot be compared with another group or moved to one, and a dynamic-length item cannot be inside a table with OCCURS DEPENDING ON.

  • ADDRESS OF cannot be taken of a dynamic-length elementary item or group in SET ptr TO ADDRESS OF. To pass it to a program that expects a fixed-length parameter, use CALL ... BY CONTENT item AS FIXED LENGTH n or BY REFERENCE item AS FIXED LENGTH n; the callee sees n characters, padded with blanks if the item is shorter, and the item’s own length is unchanged afterwards.

  • The SET LENGTH OF statement described above is how a program changes the length directly; the COBOL 2023 standard adds SET of a dynamic-length item’s length.

GnuCOBOL 3.2.0 rejects the clause and the SET. Compiling this program (deliberately wrong for that compiler):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. DYN1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-NOTICE               PIC X DYNAMIC LENGTH LIMIT 200.
       PROCEDURE DIVISION.
           MOVE "Overdue: please return" TO WS-NOTICE
           SET LENGTH OF WS-NOTICE TO 7
           DISPLAY WS-NOTICE
           DISPLAY LENGTH OF WS-NOTICE
           STOP RUN.

gives the real messages:

dyn1.cob:5: error: syntax error, unexpected DYNAMIC
dyn1.cob:8: error: syntax error, unexpected LENGTH OF

The nearest GnuCOBOL feature is ANY LENGTH on a LINKAGE SECTION item, which lets a subprogram accept an alphanumeric argument of any size and ask for its length with FUNCTION LENGTH. It does not let the item change length, but it covers the common use "this subprogram takes a text of unknown size". In Enterprise COBOL, ANY LENGTH does not exist; use a dynamic-length parameter or pass the length explicitly.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ANYLEN1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-DAYS                 PIC 9(3) VALUE 14.
       PROCEDURE DIVISION.
           CALL "SHOWTXT" USING "Overdue: please return" WS-DAYS
           STOP RUN.
       END PROGRAM ANYLEN1.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SHOWTXT.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-TEXT                 PIC X ANY LENGTH.
       01  LS-DAYS                 PIC 9(3).
       PROCEDURE DIVISION USING LS-TEXT LS-DAYS.
           DISPLAY "Length received: " FUNCTION LENGTH (LS-TEXT)
           DISPLAY LS-TEXT (1:7) " after " LS-DAYS " days"
           GOBACK.
       END PROGRAM SHOWTXT.
Length received: 000000022
Overdue after 014 days

Strings and dynamic items are used together in Strings: STRING, UNSTRING and INSPECT, and UTF-8 items (PIC U) are explained in National, UTF-8 and Code Pages.

When a COBOL program needs pointers

Most Circulation programs never touch an address. The cases that do:

  • Calling C (and other languages). C functions return and expect addresses. A CALL that returns a pointer puts it in a USAGE POINTER item with RETURNING; you then base a LINKAGE item on it. The example asks the C library for an environment variable and reads the text the returned pointer designates. The C string ends with a NUL byte (X"00"), so the program finds its length with INSPECT ... BEFORE INITIAL X"00". The 80-byte LINKAGE item is only a window; reading it past the end of the real string is reading memory that is not yours, which is why only the first WS-LEN bytes are used.

          * Circulation: read an environment variable through a C pointer
           IDENTIFICATION DIVISION.
           PROGRAM-ID. CPTR1.
           DATA DIVISION.
           WORKING-STORAGE SECTION.
           77  WS-NAME                 PIC X(10) VALUE "CIRC_DIR".
           77  WS-VALUE                PIC X(20) VALUE "/data/circ".
           77  WS-C-NAME               PIC X(11).
           77  WS-ENV-PTR              USAGE POINTER VALUE NULL.
           77  WS-LEN                  PIC 9(4) BINARY.
           LINKAGE SECTION.
           01  LS-ENV-TEXT             PIC X(80).
           PROCEDURE DIVISION.
           MAIN-PARA.
               DISPLAY WS-NAME UPON ENVIRONMENT-NAME
               DISPLAY WS-VALUE UPON ENVIRONMENT-VALUE
               STRING FUNCTION TRIM (WS-NAME) X"00" DELIMITED BY SIZE
                   INTO WS-C-NAME
               CALL "getenv" USING BY REFERENCE WS-C-NAME
                   RETURNING WS-ENV-PTR
               IF WS-ENV-PTR = NULL
                   DISPLAY "CIRC_DIR is not set"
               ELSE
                   SET ADDRESS OF LS-ENV-TEXT TO WS-ENV-PTR
                   MOVE 0 TO WS-LEN
                   INSPECT LS-ENV-TEXT TALLYING WS-LEN
                       FOR CHARACTERS BEFORE INITIAL X"00"
                   DISPLAY "CIRC_DIR=" LS-ENV-TEXT (1:WS-LEN)
               END-IF
               STOP RUN.
    CIRC_DIR=/data/circ
  • Variably located data. A record whose position depends on OCCURS DEPENDING ON cannot be addressed by name after the variable part, so IBM lists pointers (limited base addressing) as the tool for passing and receiving such an area. See Tables and OCCURS.

  • CICS. A CICS program is given its terminal and communication areas through the LINKAGE SECTION (for example DFHCOMMAREA) and normally obtains working storage with EXEC CICS GETMAIN SET(ptr), which stores an address in a pointer that you then use in SET ADDRESS OF. Check the CICS documentation for the storage services to use in place of ALLOCATE. This is z/OS only and not run here (statement forms not verified against the CICS Application Programming Reference); see CICS Online Programming.

  • IMS. An IMS program receives the addresses of its program communication blocks (PCBs) as USING parameters on its entry point, or retrieves them through the application interface block, and describes each PCB with a LINKAGE SECTION item. This is z/OS only and not run here (the exact calls are not verified against the IMS documentation); see IMS DL/I Overview.

  • Callbacks and plug-ins. A program pointer (previous section) is how a table of programs, or a callback received from another language, is invoked.

For the Java and Python side of the same boundary, which uses the Java Native Interface and ctypes rather than raw addresses, see Interoperability with C, Java and Python.

Choosing between pointers and the alternatives

Need Prefer Why

A table of at most a few thousand rows

OCCURS (or OCCURS DEPENDING ON)

No addresses, subscript checks available, same on every compiler.

A table that must grow to an unknown size

OCCURS 1 TO UNBOUNDED with ALLOCATE (Enterprise COBOL) or a pointer block sized by a first pass

The memory is exactly what is used.

Records that arrive in sequence and are processed once

A file, not memory

No storage limit; see Sequential Files.

A string of unknown length

DYNAMIC LENGTH (z/OS) or ANY LENGTH on a parameter (GnuCOBOL)

The run time manages the buffer.

A choice of program at run time

CALL identifier, or a PROCEDURE-POINTER when called often

CALL identifier is simpler; a pointer saves the repeated lookup.

Calling C that returns or expects an address

USAGE POINTER and SET ADDRESS OF

There is no other way.

The general advice is to reach for a pointer only when the language gives no named way to do the job, to keep the ALLOCATE and the FREE in the same program where possible, to set freed pointers to NULL, and to wrap the pointer logic in one small subprogram so the rest of the application never sees an address.