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 |
|
|
Copy a pointer |
|
|
Compare two pointers |
|
Only equality and inequality make sense; there is no ordering. |
Take the address of a data item |
|
|
Point a |
|
|
Get dynamic storage |
|
Below. |
Pass an address to another program |
|
The callee declares the parameter as |
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-STORAGEor theLINKAGE SECTION:SET ptr TO ADDRESS OF item, orCALL "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 SECTIONitem:SET ADDRESS OF item TO ptr. This is how aLINKAGEitem 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 intoptr. WithINITIALIZEDthe 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 ofitemis obtained anditemis based on it, as if you had writtenSET ADDRESS OF item TOthe new address. WithINITIALIZED, the item is initialised asINITIALIZE item WITH FILLER ALL TO VALUE THEN TO DEFAULTwould do, soVALUEclauses take effect.RETURNINGis 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 |
|---|---|---|
|
A level 01 or 77 |
A level 01 or 77 item with the |
|
A |
A pointer, or a |
Where the storage comes from |
The Language Environment heap; the optional |
The run-time library’s allocator. The |
Lifetime |
Until |
Release it with |
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.
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 |
|---|---|---|
|
|
|
Arithmetic on a pointer |
|
|
Numeric value into a pointer |
|
|
|
|
|
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 anENTRYstatement, or of an entry point in a non-COBOL program, orNULL. It may be used in a relation condition (equality only), as an operand ofCALLthat names the program to call, and in theUSINGphrase ofENTRYandPROCEDURE 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) |
|---|---|---|
|
4 bytes |
8 bytes |
|
4 bytes |
8 bytes |
|
4 bytes (synonym of |
4 bytes, holds an address below the bar |
|
|
|
|
below the bar ( |
above the bar for |
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-STORAGEorLINKAGE SECTION. In theFILE SECTIONit must be level 01 or 77. InLINKAGEit must also be aUSING BY REFERENCEparameter or theRETURNINGitem. -
Supported statements are
ACCEPT(format 2),CALL,CANCEL,DISPLAY,EVALUATE,MOVE,SET,STOP,STRINGandUNSTRING. -
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 OFcannot be taken of a dynamic-length elementary item or group inSET ptr TO ADDRESS OF. To pass it to a program that expects a fixed-length parameter, useCALL ... BY CONTENT item AS FIXED LENGTH norBY REFERENCE item AS FIXED LENGTH n; the callee seesncharacters, padded with blanks if the item is shorter, and the item’s own length is unchanged afterwards. -
The
SET LENGTH OFstatement described above is how a program changes the length directly; the COBOL 2023 standard addsSETof 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
CALLthat returns a pointer puts it in aUSAGE POINTERitem withRETURNING; you then base aLINKAGEitem 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 withINSPECT ... BEFORE INITIAL X"00". The 80-byteLINKAGEitem 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 firstWS-LENbytes 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/circSource: GnuCOBOL Manual, CALL statement and USAGE POINTER and Enterprise COBOL 6.5 Language Reference, CALL statement and "FUNCTION-POINTER phrase" (the z/OS equivalent passes a function pointer to C; not run here)
-
Variably located data. A record whose position depends on
OCCURS DEPENDING ONcannot 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 exampleDFHCOMMAREA) and normally obtains working storage withEXEC CICS GETMAIN SET(ptr), which stores an address in a pointer that you then use inSET ADDRESS OF. Check the CICS documentation for the storage services to use in place ofALLOCATE. 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
USINGparameters on its entry point, or retrieves them through the application interface block, and describes each PCB with aLINKAGE SECTIONitem. 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 |
|
No addresses, subscript checks available, same on every compiler. |
A table that must grow to an unknown size |
|
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 |
|
The run time manages the buffer. |
A choice of program at run time |
|
|
Calling C that returns or expects an address |
|
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.