In the early decades of COBOL programming, it was common to find programs spanning tens of thousands of lines -- monolithic behemoths that handled input validation, business logic, file processing, report generation, and error handling all within a...
In This Chapter
- Introduction: Why Modular Programming Matters in COBOL
- 17.1 The CALL Statement: Calling External Subprograms
- 17.2 Static vs. Dynamic CALL
- 17.3 The LINKAGE SECTION: Defining Parameters
- 17.4 PROCEDURE DIVISION USING: Declaring the Parameter List
- 17.5 Parameter Passing Modes
- 17.6 The LENGTH OF Special Register
- 17.7 RETURN-CODE Special Register
- 17.8 The RETURNING Clause (COBOL 2002+)
- 17.9 GOBACK vs. STOP RUN in Subprograms
- 17.10 The CANCEL Statement
- 17.11 Initial State and the IS INITIAL Clause
- 17.12 Nested Programs (COBOL-85)
- 17.13 Recursive Programs (COBOL 2002+)
- 17.14 Calling Conventions and Parameter Alignment
- 17.15 Interface to Other Languages
- 17.16 Compilation and Linking
- 17.17 Common Subprogram Patterns
- 17.18 Designing a Subprogram Interface
- 17.19 Best Practices for Modular COBOL Design
- 17.20 Putting It All Together: Complete Static Call Example
- 17.21 Complete Dynamic Call Example
- 17.22 Expected Output
- Summary
Chapter 17: Subprograms and the CALL Statement
Introduction: Why Modular Programming Matters in COBOL
In the early decades of COBOL programming, it was common to find programs spanning tens of thousands of lines -- monolithic behemoths that handled input validation, business logic, file processing, report generation, and error handling all within a single source file. These programs were difficult to understand, painful to modify, and nearly impossible to test in isolation. When a change was needed in a date validation routine used in twenty different places, a programmer had to locate and modify every occurrence, hoping nothing was missed.
Modular programming solves these problems by decomposing large programs into smaller, self-contained units called subprograms (also known as subroutines or called programs). Each subprogram performs a specific, well-defined task and communicates with its caller through a formal parameter interface. This approach delivers several critical benefits:
- Reusability: A date validation subprogram written once can be called from dozens of different main programs without duplicating a single line of code.
- Maintainability: When the validation rules change, you modify one subprogram. Every caller automatically gets the updated behavior.
- Testability: Each subprogram can be tested independently with a simple test driver, long before the full system is assembled.
- Division of labor: Different programmers can work on different subprograms simultaneously, each with a clear interface contract.
- Encapsulation: The internal logic of a subprogram is hidden from callers. As long as the interface remains stable, the implementation can be completely rewritten.
COBOL has supported external subprogram calls since its earliest standards, and this support has grown richer with each revision. The COBOL-85 standard introduced nested programs and the COMMON clause. COBOL 2002 added the RETURNING clause and recursive programs. IBM Enterprise COBOL, Micro Focus COBOL, and GnuCOBOL all provide robust implementations of these features.
This chapter covers everything you need to write, call, and manage COBOL subprograms: from the basic CALL statement through advanced topics like parameter passing modes, the CANCEL statement, nested programs, and inter-language calls.
17.1 The CALL Statement: Calling External Subprograms
The CALL statement transfers control from one COBOL program (the caller) to another (the called program or subprogram). When the subprogram finishes its work, it returns control to the statement following the CALL.
Basic Syntax
CALL 'SUBPROG' USING parameter-1 parameter-2 ...
Or with a variable:
CALL WS-PROGRAM-NAME USING parameter-1 parameter-2 ...
The CALL statement has several optional phrases:
CALL {literal | identifier}
[USING {[BY REFERENCE] | BY CONTENT | BY VALUE} parameter ...]
[RETURNING identifier]
[ON EXCEPTION imperative-statement]
[NOT ON EXCEPTION imperative-statement]
[ON OVERFLOW imperative-statement]
[END-CALL]
The ON EXCEPTION phrase (or the older ON OVERFLOW) executes when the called program cannot be found or loaded. This is particularly important for dynamic calls where the program might not exist in the load library:
CALL WS-PROG-NAME USING WS-PARAMETERS
ON EXCEPTION
DISPLAY 'ERROR: Program ' WS-PROG-NAME
' not found'
MOVE 99 TO WS-RETURN-CODE
NOT ON EXCEPTION
MOVE RETURN-CODE TO WS-RETURN-CODE
END-CALL
17.2 Static vs. Dynamic CALL
COBOL supports two fundamentally different ways of calling subprograms, and the distinction has significant implications for performance, flexibility, and deployment.
Static CALL (CALL Literal with NODYNAM)
A static call uses a literal (a string enclosed in quotes) as the program name and is resolved at link-edit time (also called bind time). The compiler generates a direct reference to the subprogram, and the linker combines the main program and subprogram into a single load module.
* Static call -- resolved at link time
CALL 'PAYCALC' USING WS-EMPLOYEE-DATA
WS-RESULT-DATA
WS-STATUS
Characteristics of static calls:
| Aspect | Static Call Behavior |
|---|---|
| Resolution | At link-edit time |
| Load module size | Larger (includes all subprograms) |
| Runtime performance | Faster (no load overhead) |
| Memory usage | All subprograms in memory at once |
| Flexibility | Less flexible; changes require re-link |
| CANCEL | Not applicable (cannot cancel) |
To compile for static calling on IBM Enterprise COBOL, use the NODYNAM compiler option:
PARM='NODYNAM,LIB,OBJECT'
See Example 1: Static Call for a complete working program.
Dynamic CALL (CALL Identifier or CALL Literal with DYNAM)
A dynamic call resolves the subprogram name at runtime. The Language Environment runtime locates the subprogram in the load library (STEPLIB, JOBLIB, or link pack area), loads it into memory, and transfers control.
There are two ways to make a dynamic call:
Method 1: Using an identifier (variable)
01 WS-PROG-NAME PIC X(08) VALUE 'PAYCALC'.
...
CALL WS-PROG-NAME USING WS-DATA
When you CALL a variable instead of a literal, the call is always dynamic regardless of compiler options.
Method 2: Using a literal with the DYNAM compiler option
* With DYNAM option, even literal calls become dynamic
CALL 'PAYCALC' USING WS-DATA
When compiled with the DYNAM option, CALL 'literal' statements are treated as dynamic calls.
Characteristics of dynamic calls:
| Aspect | Dynamic Call Behavior |
|---|---|
| Resolution | At runtime |
| Load module size | Smaller (only main program) |
| Runtime performance | Slightly slower (load overhead on first call) |
| Memory usage | Subprograms loaded on demand |
| Flexibility | Very flexible; can swap subprogram without re-linking main |
| CANCEL | Supported (release from memory) |
See Example 2: Dynamic Call for a program that dynamically selects which subprogram to call based on transaction type.
Compiler Options Controlling Static/Dynamic (IBM Enterprise COBOL)
| Option | Effect |
|---|---|
| NODYNAM | CALL 'literal' is static (default on many systems) |
| DYNAM | CALL 'literal' is treated as dynamic |
| N/A | CALL identifier is always dynamic regardless of options |
Important: The DYNAM/NODYNAM option only affects CALL 'literal' statements. A CALL identifier is always dynamic. Many shops standardize on NODYNAM and use identifiers when they specifically want dynamic behavior.
When to Use Each
Use static calls when: - The subprogram is always needed and will always be present - Maximum runtime performance is critical - You want the simplest compile-link-go process - The subprogram is small and the load module size increase is acceptable
Use dynamic calls when: - The subprogram may or may not be needed at runtime - You want to replace subprograms without re-linking the main program - Memory conservation is important (load on demand) - You need to CANCEL and reinitialize subprograms - The system uses a plugin or strategy pattern where different subprograms are selected at runtime
17.3 The LINKAGE SECTION: Defining Parameters
The LINKAGE SECTION in a subprogram defines the data items that the subprogram receives from its caller. These items do not allocate memory in the subprogram; instead, they describe the layout of memory that belongs to the calling program.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LOCAL-DATA PIC X(10).
LINKAGE SECTION.
01 LS-INPUT-RECORD.
05 LS-FIELD-A PIC X(10).
05 LS-FIELD-B PIC 9(05).
01 LS-OUTPUT-AREA.
05 LS-RESULT PIC 9(07)V99.
05 LS-STATUS PIC 9(02).
Key points about the LINKAGE SECTION:
-
No memory allocation: LINKAGE SECTION items describe data that resides in the caller's memory. The subprogram accesses the caller's data directly (for BY REFERENCE parameters).
-
Naming convention: Many shops prefix LINKAGE SECTION items with
LS-to distinguish them from WORKING-STORAGE items (WS-). This is a convention, not a requirement. -
Data description must match: The PICTURE clauses and structure in the LINKAGE SECTION must be compatible with what the caller passes. If the caller passes a
PIC X(20)field and the LINKAGE SECTION describes it asPIC 9(20), the data will be reinterpreted, potentially causing errors. -
Group items are common: It is common practice to pass group items (records) rather than many individual elementary items. This reduces the number of parameters and makes the interface easier to manage.
17.4 PROCEDURE DIVISION USING: Declaring the Parameter List
The subprogram's PROCEDURE DIVISION must include a USING clause that names the LINKAGE SECTION items it expects to receive:
PROCEDURE DIVISION USING LS-INPUT-RECORD
LS-OUTPUT-AREA.
Rules for PROCEDURE DIVISION USING:
- Each item named in USING must be defined in the LINKAGE SECTION (01 or 77 level).
- The order of parameters must match the order in the caller's
CALL ... USING. - The number of parameters must match between caller and callee.
- If any parameter is BY VALUE, it must be specified in the PROCEDURE DIVISION USING as well.
Matching Caller and Callee
The parameter lists are matched positionally, not by name. The first parameter in CALL ... USING corresponds to the first parameter in PROCEDURE DIVISION USING, and so on:
* In the calling program:
CALL 'MYSUB' USING WS-CUSTOMER-REC
WS-RESULT-AREA
WS-ERROR-CODE
* In the called program:
PROCEDURE DIVISION USING LS-INPUT-DATA
LS-OUTPUT-DATA
LS-STATUS.
* WS-CUSTOMER-REC <--> LS-INPUT-DATA (1st parameter)
* WS-RESULT-AREA <--> LS-OUTPUT-DATA (2nd parameter)
* WS-ERROR-CODE <--> LS-STATUS (3rd parameter)
The names do not need to match. What matters is that the size and structure of each parameter pair are compatible.
17.5 Parameter Passing Modes
COBOL provides three parameter passing modes, each with different semantics for how the caller's data is shared with the called program.
BY REFERENCE (Default)
When a parameter is passed BY REFERENCE, the subprogram receives the memory address of the caller's data. The caller and callee share the same physical memory location. Any modification the subprogram makes to the parameter is immediately visible to the caller.
CALL 'MYSUB' USING BY REFERENCE WS-COUNTER
Since BY REFERENCE is the default, you can omit it:
CALL 'MYSUB' USING WS-COUNTER
When to use BY REFERENCE: - When the subprogram needs to return data to the caller through the parameter - When you want maximum efficiency (no data copying) - For large data structures where copying would be expensive
BY CONTENT
When a parameter is passed BY CONTENT, the compiler creates a temporary copy of the caller's data and passes the address of that copy to the subprogram. The subprogram can read and even modify the copy, but the caller's original data remains unchanged.
CALL 'MYSUB' USING BY CONTENT WS-ORIGINAL-VALUE
When to use BY CONTENT: - When you want to protect the caller's data from modification - When passing literals or expressions that cannot be modified - When you need read-only semantics for safety
Important: BY CONTENT does not mean the parameter is read-only in the subprogram. The subprogram can modify the temporary copy. The protection is on the caller's side -- the original variable will not be changed.
BY VALUE
When a parameter is passed BY VALUE, the actual value is placed directly on the call stack (not an address). This is the calling convention used by C and other languages.
CALL 'MYSUB' USING BY VALUE WS-INTEGER-FIELD
In the called program's PROCEDURE DIVISION, BY VALUE must be explicitly stated:
PROCEDURE DIVISION USING BY VALUE LS-INTEGER-FIELD.
Restrictions on BY VALUE: - The data item should be a binary integer type (COMP, COMP-5, BINARY) - Group items cannot be passed BY VALUE - BY VALUE is primarily used when calling C functions or other non-COBOL programs - Not all COBOL compilers support BY VALUE for COBOL-to-COBOL calls
See Example 3: Parameter Passing Modes for a working demonstration of all three modes.
Mixing Passing Modes
You can mix passing modes in a single CALL statement. The passing mode applies to all subsequent parameters until a different mode is specified:
CALL 'MYSUB' USING BY REFERENCE WS-OUTPUT-AREA
BY CONTENT WS-INPUT-DATA
WS-CONTROL-FLAGS
BY REFERENCE WS-STATUS
In this example, WS-OUTPUT-AREA and WS-STATUS are BY REFERENCE, while WS-INPUT-DATA and WS-CONTROL-FLAGS are BY CONTENT (the BY CONTENT carries forward until changed).
Summary Table
| Mode | Caller's Data Modified? | Mechanism | Primary Use |
|---|---|---|---|
| BY REFERENCE | Yes | Passes address of original | Output parameters, shared data |
| BY CONTENT | No | Passes address of a copy | Input-only parameters, protection |
| BY VALUE | No | Passes value on stack | C interoperability |
17.6 The LENGTH OF Special Register
When passing data to a subprogram, you sometimes need to communicate the length of a variable-length or variable-format data area. The LENGTH OF special register provides the number of bytes occupied by a data item:
01 WS-BUFFER PIC X(500).
01 WS-BUF-LENGTH PIC 9(04) COMP.
...
MOVE LENGTH OF WS-BUFFER TO WS-BUF-LENGTH
CALL 'PROCESS' USING WS-BUFFER
WS-BUF-LENGTH
You can also pass LENGTH OF directly:
CALL 'PROCESS' USING WS-BUFFER
BY CONTENT LENGTH OF WS-BUFFER
This pattern is especially useful when: - The subprogram handles buffers of varying sizes - You are interfacing with system services that require explicit length parameters - The subprogram needs to validate that received data fits within expected bounds
17.7 RETURN-CODE Special Register
The RETURN-CODE special register is a numeric field that serves as a communication channel between a calling program and a called program. When a subprogram sets RETURN-CODE before returning with GOBACK, the calling program can examine RETURN-CODE after the CALL to determine the outcome.
In the called program:
IF validation-failed
MOVE 8 TO RETURN-CODE
ELSE
MOVE 0 TO RETURN-CODE
END-IF
GOBACK
In the calling program:
CALL 'VALIDATE' USING WS-DATA
IF RETURN-CODE > 0
DISPLAY 'Validation failed with RC=' RETURN-CODE
END-IF
Conventional Return Code Values
Many mainframe shops follow the IBM convention for return code values:
| Value | Meaning |
|---|---|
| 0 | Success; no errors |
| 4 | Warning; processing completed with minor issues |
| 8 | Error; processing may be incomplete |
| 12 | Severe error; significant problems |
| 16 | Critical error; processing could not continue |
Following this convention makes programs easier to understand and maintain because the meaning of return codes is consistent across the entire system.
17.8 The RETURNING Clause (COBOL 2002+)
The RETURNING clause, introduced in COBOL 2002, provides a more explicit and structured way to return a value from a subprogram:
In the called program:
LINKAGE SECTION.
01 LS-INPUT PIC X(20).
01 LS-RETURN-VAL PIC S9(04) COMP.
PROCEDURE DIVISION USING LS-INPUT
RETURNING LS-RETURN-VAL.
In the calling program:
CALL 'VALIDATE' USING WS-INPUT-DATA
RETURNING WS-RESULT
The RETURNING clause is cleaner than using RETURN-CODE because: - The return value is explicitly part of the interface definition - It can be any data type, not just a numeric code - It makes the subprogram's contract more visible and self-documenting
See Example 4: Return Code for a demonstration of both RETURN-CODE and RETURNING.
Note: When RETURNING is used, the value is also placed in the RETURN-CODE special register for backward compatibility (on most compilers).
17.9 GOBACK vs. STOP RUN in Subprograms
When a subprogram finishes its work, it must return control to the calling program. There are two statements that can end a program's execution, but they have very different effects:
GOBACK
GOBACK returns control to the statement following the CALL in the calling program. The run unit continues. GOBACK is the correct way to exit a subprogram:
PROCEDURE DIVISION USING LS-PARAMETERS.
0000-PROCESS.
PERFORM actual-work
GOBACK.
GOBACK can also be used in a main program, where it behaves like STOP RUN.
STOP RUN
STOP RUN terminates the entire run unit -- the main program and all subprograms. If a subprogram executes STOP RUN, the calling program does not get control back. The entire application ends.
Rule: Never use STOP RUN in a subprogram. Always use GOBACK.
The only place STOP RUN belongs is in the main program's exit logic:
* In the main program -- STOP RUN is appropriate here
0000-MAIN.
PERFORM process-all-records
STOP RUN.
EXIT PROGRAM
EXIT PROGRAM is an older alternative to GOBACK in a subprogram. In a called program, EXIT PROGRAM returns control to the caller. In a main program, EXIT PROGRAM is ignored and execution continues to the next statement. Most modern COBOL shops prefer GOBACK because it works correctly in both contexts.
17.10 The CANCEL Statement
The CANCEL statement releases a dynamically loaded subprogram from memory. The next time the subprogram is called, it will be reloaded from the load library, and its WORKING-STORAGE will be reinitialized to the values specified in VALUE clauses.
CANCEL 'SUBPROG'
Or with a variable:
CANCEL WS-PROGRAM-NAME
Why CANCEL Matters
Without CANCEL, a subprogram's WORKING-STORAGE persists between calls. This means:
- Counters keep counting
- Flags remain set
- Accumulators retain their totals
- File status indicators stay as they were
This persistence is often useful -- for example, a logging subprogram that maintains a count of how many entries have been written. But sometimes you need a fresh start. CANCEL provides that.
Behavior Summary
| Scenario | WORKING-STORAGE Behavior |
|---|---|
| First call to subprogram | Initialized per VALUE clauses |
| Subsequent calls (no CANCEL) | Retains values from previous call |
| Call after CANCEL | Reinitialized per VALUE clauses |
| Program with IS INITIAL | Always reinitialized on every call |
See Example 5: CANCEL Statement for a working demonstration.
Restrictions
- You cannot CANCEL a statically linked subprogram (it has no meaning since the subprogram is part of the load module).
- You cannot CANCEL a program that is currently active (still on the call stack).
- CANCEL has no effect on a program that has not been loaded.
17.11 Initial State and the IS INITIAL Clause
WORKING-STORAGE Persistence
By default, a subprogram's WORKING-STORAGE values persist across multiple calls. This is a fundamental characteristic of COBOL subprograms and a common source of both useful behavior and subtle bugs.
Consider a subprogram with this WORKING-STORAGE:
WORKING-STORAGE SECTION.
01 WS-CALL-COUNT PIC 9(06) VALUE 0.
01 WS-LAST-ERROR PIC X(50) VALUE SPACES.
On the first call, WS-CALL-COUNT is 0 and WS-LAST-ERROR is spaces. If the subprogram increments WS-CALL-COUNT and moves an error message to WS-LAST-ERROR, those values will still be there on the second call.
IS INITIAL Clause
If you want a subprogram to start fresh on every call -- as if it were being loaded for the first time -- use the IS INITIAL clause on PROGRAM-ID:
IDENTIFICATION DIVISION.
PROGRAM-ID. MYSUB IS INITIAL.
With IS INITIAL: - WORKING-STORAGE is reinitialized per VALUE clauses on every call - There is a performance cost (reinitialization takes time) - You do not need to CANCEL the program to reset its state
When to use IS INITIAL: - When the subprogram must have no "memory" of previous calls - When persistent state would cause bugs (e.g., accumulator that should be fresh each time) - When you want guaranteed predictable behavior regardless of calling sequence
17.12 Nested Programs (COBOL-85)
COBOL-85 introduced nested programs (also called contained programs), which allow you to define multiple programs within a single source file. Nested programs are enclosed between the parent program's IDENTIFICATION DIVISION and its END PROGRAM marker.
Basic Structure
IDENTIFICATION DIVISION.
PROGRAM-ID. MAINPROG.
...
PROCEDURE DIVISION.
CALL 'SUBPROG1' USING WS-DATA
STOP RUN.
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPROG1.
...
PROCEDURE DIVISION USING LS-DATA.
...
GOBACK.
END PROGRAM SUBPROG1.
END PROGRAM MAINPROG.
Visibility Rules
By default, a nested program can only be called by its immediately containing (parent) program. Sibling programs cannot call each other unless one of them has the COMMON clause.
The COMMON Clause
The COMMON clause on PROGRAM-ID makes a nested program callable by its sibling programs, not just the parent:
PROGRAM-ID. LOGENTRY IS COMMON.
With COMMON, any program contained within the same parent can call LOGENTRY. This is useful for utility subprograms (logging, error handling) that multiple siblings need to access.
The GLOBAL Clause
The GLOBAL clause on a data item in the parent program makes that item visible to all contained programs without passing it as a parameter:
* In the parent program:
01 WS-ERROR-COUNT PIC 9(06) VALUE 0 IS GLOBAL.
Any nested program can reference WS-ERROR-COUNT directly. This provides a form of shared state without explicit parameter passing.
Use GLOBAL sparingly: While convenient, GLOBAL data can create tight coupling between programs and make it harder to reuse nested programs in other contexts. Prefer explicit parameter passing when practical.
See Example 6: Nested Programs for a complete demonstration of END PROGRAM, COMMON, and GLOBAL.
Advantages of Nested Programs
- Single compilation unit: All programs compiled together; no separate link step for nested programs
- Encapsulation: Internal helper programs are hidden from the outside world
- GLOBAL data: Controlled data sharing without parameter passing
- COMMON programs: Cross-sibling access to utility routines
Limitations
- Cannot be independently compiled or replaced
- All changes require recompiling the entire source file
- Less reusable than external subprograms (tied to their containing program)
- Source file can become very large
17.13 Recursive Programs (COBOL 2002+)
COBOL 2002 introduced the ability to write recursive programs -- programs that call themselves either directly or indirectly. To enable recursion, the PROGRAM-ID must include the RECURSIVE attribute:
IDENTIFICATION DIVISION.
PROGRAM-ID. FACTORIAL IS RECURSIVE.
How Recursion Works in COBOL
With the RECURSIVE attribute, each invocation of the program gets its own copy of LOCAL-STORAGE SECTION data. Note that WORKING-STORAGE is shared across all invocations (it is allocated once), while LOCAL-STORAGE is unique to each invocation:
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SHARED-COUNT PIC 9(04) VALUE 0.
LOCAL-STORAGE SECTION.
01 LS-LOCAL-DATA PIC 9(10) VALUE 0.
Factorial Example
IDENTIFICATION DIVISION.
PROGRAM-ID. FACTORIAL IS RECURSIVE.
DATA DIVISION.
LOCAL-STORAGE SECTION.
01 LS-N PIC 9(04).
01 LS-RESULT PIC 9(18).
01 LS-SUB-RESULT PIC 9(18).
LINKAGE SECTION.
01 LK-INPUT PIC 9(04).
01 LK-OUTPUT PIC 9(18).
PROCEDURE DIVISION USING LK-INPUT LK-OUTPUT.
MOVE LK-INPUT TO LS-N
IF LS-N <= 1
MOVE 1 TO LK-OUTPUT
ELSE
SUBTRACT 1 FROM LS-N GIVING LS-N
CALL 'FACTORIAL' USING LS-N LS-SUB-RESULT
ADD 1 TO LS-N
COMPUTE LK-OUTPUT = LS-N * LS-SUB-RESULT
END-IF
GOBACK.
Caution: Recursion is rarely used in production COBOL. Most business problems are better solved with iterative approaches. Use recursion only when the problem is inherently recursive (tree traversal, hierarchical data processing) and the depth is bounded.
17.14 Calling Conventions and Parameter Alignment
When calling between COBOL programs, the compiler handles parameter alignment and matching automatically. However, there are important conventions to follow:
Group Items vs. Elementary Items
Passing a group item is generally preferred over passing many elementary items:
* PREFERRED: Pass a group item
01 WS-EMPLOYEE-RECORD.
05 WS-EMP-ID PIC X(06).
05 WS-EMP-NAME PIC X(30).
05 WS-EMP-SALARY PIC 9(07)V99.
CALL 'PROCESS' USING WS-EMPLOYEE-RECORD
* LESS PREFERRED: Pass individual fields
CALL 'PROCESS' USING WS-EMP-ID
WS-EMP-NAME
WS-EMP-SALARY
Passing group items: - Reduces the number of parameters (fewer things to match) - Makes it easier to add fields without changing every CALL statement - Encourages defining interface copybooks (see Section 17.18)
Parameter Size Matching
The compiler does not verify that the caller's parameter sizes match the callee's LINKAGE SECTION sizes. This is the programmer's responsibility. A mismatch can cause: - Truncation: If the callee's item is shorter, it only sees part of the data - Overrun: If the callee's item is longer, it reads past the end of the caller's data (potentially reading garbage or causing an abend)
Best practice: Use copybooks to define the parameter structures and include the same copybook in both the calling and called programs:
* In both programs:
COPY EMPPARMS.
17.15 Interface to Other Languages
COBOL programs can call subprograms written in other languages, and other languages can call COBOL subprograms. The most common cross-language interactions on the mainframe are with C and Assembler.
Calling C Functions from COBOL
To call a C function, use BY VALUE for parameters that C expects as values:
01 WS-INPUT-VAL PIC S9(09) COMP-5.
01 WS-RESULT PIC S9(09) COMP-5.
CALL 'myfunction' USING BY VALUE WS-INPUT-VAL
RETURNING WS-RESULT
Important considerations for C interoperability:
- C expects parameters BY VALUE by default; COBOL's default is BY REFERENCE
- Use COMP-5 (native binary) for integer parameters to match C's int
- String parameters should be null-terminated when calling C (append X'00')
- C function names are case-sensitive; COBOL typically folds to uppercase
Calling Assembler Routines
Assembler routines are called like any other subprogram:
CALL 'ASMUTIL' USING WS-PARM-LIST
The assembler routine must follow the standard OS linkage conventions (save registers, establish addressability, restore registers). Parameters are passed BY REFERENCE by default, which matches the standard addressing convention.
17.16 Compilation and Linking
IBM z/OS: Separate Compilation
On IBM mainframes, COBOL programs are compiled individually and then linked together. The standard process involves:
- Compile: The COBOL compiler (IGYCRCTL) produces an object module
- Link-edit: The linkage editor (IEWL) combines object modules into a load module
- Execute: The load module is run
For static calls, all object modules are included in a single link-edit step:
//SYSLIN DD *
INCLUDE MAINOBJ
INCLUDE SUBOBJ
ENTRY MAINPROG
NAME MAINPROG(R)
/*
For dynamic calls, each subprogram is linked into its own load module and placed in a load library:
//SYSLMOD DD DSN=MY.LOADLIB(SUBPROG),DISP=SHR
IBM Cataloged Procedures
IBM provides cataloged procedures that simplify the compile-link-go process:
| Procedure | Steps |
|---|---|
| IGYWC | Compile only |
| IGYWCL | Compile and link-edit |
| IGYWCLG | Compile, link-edit, and go (execute) |
| IGYWCG | Compile and go (no explicit link step) |
See Example 1 JCL and Example 2 JCL for complete JCL for static and dynamic call scenarios.
GnuCOBOL: Compiling on Open Systems
GnuCOBOL provides a simpler compilation model:
Compile and link a main program with subprograms (static):
cobc -x -o myprog main.cob sub1.cob sub2.cob
The -x flag creates an executable. All source files are compiled and linked together.
Compile subprograms as dynamically loadable modules:
cobc -m sub1.cob # Produces sub1.so (Linux) or sub1.dll (Windows)
cobc -m sub2.cob # Produces sub2.so or sub2.dll
cobc -x main.cob # Main program only
The -m flag creates a dynamically loadable module. At runtime, GnuCOBOL searches for the module in the current directory, COB_LIBRARY_PATH, or other configured locations.
Compile as a static library:
cobc -c sub1.cob sub2.cob # Produces .o object files
ar rcs libmysubs.a sub1.o sub2.o
cobc -x main.cob -L. -lmysubs # Link against the library
17.17 Common Subprogram Patterns
Validation Subprogram
A validation subprogram checks input data and returns a status code:
PROCEDURE DIVISION USING LS-INPUT-DATA
LS-ERROR-TABLE
LS-ERROR-COUNT.
0000-VALIDATE.
MOVE 0 TO LS-ERROR-COUNT
PERFORM 1000-CHECK-REQUIRED-FIELDS
PERFORM 2000-CHECK-DATA-RANGES
PERFORM 3000-CHECK-CROSS-FIELD-RULES
GOBACK.
Date Calculation Subprogram
Date subprograms are among the most commonly reused components:
* Calculate days between two dates
PROCEDURE DIVISION USING LS-START-DATE
LS-END-DATE
LS-DAYS-BETWEEN
LS-STATUS.
0000-CALC-DAYS.
COMPUTE LS-DAYS-BETWEEN =
FUNCTION INTEGER-OF-DATE(LS-END-DATE) -
FUNCTION INTEGER-OF-DATE(LS-START-DATE)
GOBACK.
Error Handler Subprogram
A centralized error handler receives error information and performs consistent handling:
PROCEDURE DIVISION USING LS-ERROR-INFO.
0000-HANDLE-ERROR.
PERFORM 1000-LOG-ERROR
PERFORM 2000-FORMAT-MESSAGE
EVALUATE LS-ERR-SEVERITY
WHEN 'W' CONTINUE
WHEN 'E' PERFORM 3000-NOTIFY-OPERATOR
WHEN 'C' PERFORM 4000-EMERGENCY-SHUTDOWN
END-EVALUATE
GOBACK.
Logging Subprogram
A logging subprogram provides consistent message formatting and output:
PROCEDURE DIVISION USING LS-LOG-LEVEL
LS-PROGRAM-NAME
LS-MESSAGE-TEXT.
0000-WRITE-LOG.
MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP
STRING WS-FORMATTED-TIMESTAMP DELIMITED SIZE
' [' DELIMITED SIZE
LS-LOG-LEVEL DELIMITED SPACES
'] ' DELIMITED SIZE
LS-PROGRAM-NAME DELIMITED SPACES
': ' DELIMITED SIZE
LS-MESSAGE-TEXT DELIMITED SPACES
INTO WS-LOG-LINE
WRITE LOG-RECORD FROM WS-LOG-LINE
GOBACK.
17.18 Designing a Subprogram Interface
Good subprogram design starts with a well-defined interface. Here are the principles that lead to maintainable, reusable subprograms.
Use Copybooks for Parameter Definitions
Define the LINKAGE SECTION structure in a copybook and include it in both the calling and called programs:
* Copybook: DATEPARM.cpy
01 DATE-VALIDATION-PARMS.
05 DVP-INPUT-DATE PIC X(10).
05 DVP-DATE-FORMAT PIC X(10).
05 DVP-OUTPUT-JULIAN PIC 9(07).
05 DVP-DAY-OF-WEEK PIC 9(01).
05 DVP-RETURN-CODE PIC 9(02).
05 DVP-ERROR-MESSAGE PIC X(50).
* In the calling program's WORKING-STORAGE:
COPY DATEPARM.
* In the called program's LINKAGE SECTION:
COPY DATEPARM.
This ensures perfect alignment and makes the interface self-documenting.
Parameter Design Guidelines
-
Include a return code: Every subprogram should return a status indicator so the caller knows whether the operation succeeded.
-
Include an error message: A human-readable error message is invaluable for debugging and operator communication.
-
Use group items: Bundle related parameters into a single group item. This simplifies the CALL statement and makes it easier to add fields later.
-
Separate input from output: Clearly delineate which parameters are input (the subprogram reads them) and which are output (the subprogram writes them). Consider using BY CONTENT for input parameters to enforce this.
-
Document the interface: Comment the copybook with the purpose of each field, valid values, and expected behavior.
-
Version the interface: If you need to change a parameter structure, consider adding a version field so the subprogram can handle both old and new callers during transition.
17.19 Best Practices for Modular COBOL Design
1. One Function per Subprogram
Each subprogram should do one thing and do it well. A subprogram named "VALIDATE-DATE" should validate dates and nothing else. If you find yourself adding transaction processing logic to a date validator, it is time to create a new subprogram.
2. Keep Parameter Lists Short
If you need more than five or six parameters, bundle them into a group item. Long parameter lists are error-prone and hard to maintain.
3. Always Return a Status Code
The caller needs to know whether the subprogram succeeded. Return 0 for success and nonzero values for specific error conditions. Document what each code means.
4. Use GOBACK, Never STOP RUN
STOP RUN in a subprogram terminates the entire run unit. GOBACK returns control properly. Always use GOBACK in subprograms.
5. Be Careful with WORKING-STORAGE Persistence
Remember that WORKING-STORAGE values persist between calls (unless CANCEL is used or IS INITIAL is specified). Initialize variables at the start of each invocation if they should not carry over:
0000-PROCESS.
INITIALIZE WS-WORK-AREAS
...
6. Handle the ON EXCEPTION Condition
Dynamic calls can fail if the subprogram is not found. Always code ON EXCEPTION for dynamic calls:
CALL WS-PROG-NAME USING WS-PARMS
ON EXCEPTION
MOVE 'Program not found' TO WS-ERROR-MSG
END-CALL
7. Match Parameter Descriptions Exactly
Use the same copybook in both the caller and the callee. If the descriptions do not match, you will get incorrect data, truncation, or storage overlays.
8. Prefer BY CONTENT for Input Parameters
If a parameter is input-only (the subprogram should not modify it), pass it BY CONTENT. This protects the caller's data and makes the intent clear.
9. Test Subprograms Independently
Write a simple test driver (main program) for each subprogram. Test it with valid data, boundary values, and invalid data before integrating it into the full system.
10. Document the Interface Contract
For each subprogram, document: - The purpose and function - Each parameter: name, type, direction (input/output/both), valid values - Return codes and their meanings - Any side effects (file I/O, database updates) - Performance characteristics (is it called once or millions of times?)
17.20 Putting It All Together: Complete Static Call Example
Let us walk through a complete example of a main program calling a subprogram using a static CALL.
The Main Program (STATICMN)
The main program prepares employee data, calls the payroll calculation subprogram, and displays the results:
IDENTIFICATION DIVISION.
PROGRAM-ID. STATICMN.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-EMPLOYEE-RECORD.
05 WS-EMP-ID PIC X(06).
05 WS-EMP-NAME PIC X(30).
05 WS-EMP-SALARY PIC 9(07)V99.
05 WS-EMP-TAX-RATE PIC 9V9(04).
01 WS-CALCULATED-FIELDS.
05 WS-GROSS-PAY PIC 9(07)V99.
05 WS-TAX-AMOUNT PIC 9(07)V99.
05 WS-NET-PAY PIC 9(07)V99.
01 WS-RETURN-STATUS PIC 9(02).
PROCEDURE DIVISION.
...
CALL 'PAYCALC' USING WS-EMPLOYEE-RECORD
WS-CALCULATED-FIELDS
WS-RETURN-STATUS
...
The Subprogram (PAYCALC)
The subprogram receives the employee data through the LINKAGE SECTION, performs calculations, and returns results:
IDENTIFICATION DIVISION.
PROGRAM-ID. PAYCALC.
DATA DIVISION.
LINKAGE SECTION.
01 LS-EMPLOYEE-RECORD.
05 LS-EMP-ID PIC X(06).
05 LS-EMP-NAME PIC X(30).
05 LS-EMP-SALARY PIC 9(07)V99.
05 LS-EMP-TAX-RATE PIC 9V9(04).
01 LS-CALCULATED-FIELDS.
05 LS-GROSS-PAY PIC 9(07)V99.
05 LS-TAX-AMOUNT PIC 9(07)V99.
05 LS-NET-PAY PIC 9(07)V99.
01 LS-RETURN-STATUS PIC 9(02).
PROCEDURE DIVISION USING LS-EMPLOYEE-RECORD
LS-CALCULATED-FIELDS
LS-RETURN-STATUS.
...
COMPUTE LS-TAX-AMOUNT =
LS-GROSS-PAY * LS-EMP-TAX-RATE
COMPUTE LS-NET-PAY =
LS-GROSS-PAY - LS-TAX-AMOUNT
MOVE 00 TO LS-RETURN-STATUS
GOBACK.
The JCL
The JCL compiles both programs, links them together, and runs the result:
//* Compile main program
//COBMAIN EXEC PGM=IGYCRCTL,PARM='NODYNAM,LIB,OBJECT'
//SYSIN DD DSN=USERID.COBOL.SOURCE(STATICMN),DISP=SHR
//* Compile subprogram
//COBSUB EXEC PGM=IGYCRCTL,PARM='NODYNAM,LIB,OBJECT'
//SYSIN DD DSN=USERID.COBOL.SOURCE(PAYCALC),DISP=SHR
//* Link both object modules together
//LKED EXEC PGM=IEWL
//SYSLIN DD *
INCLUDE MAINOBJ
INCLUDE SUBOBJ
ENTRY STATICMN
/*
//* Run the linked program
//GO EXEC PGM=STATICMN
See the full source code files: - example-01-static-call.cob -- Main program - example-01-subprog.cob -- Subprogram - example-01-static-call.jcl -- Complete JCL
17.21 Complete Dynamic Call Example
The dynamic call example demonstrates a transaction processing system where different subprograms are selected at runtime:
* Determine which subprogram to call
EVALUATE TRUE
WHEN WS-TRANS-DEPOSIT
WHEN WS-TRANS-WITHDRAW
MOVE 'SUBPRGA' TO WS-PROG-NAME
WHEN WS-TRANS-INQUIRY
MOVE 'SUBPRGB' TO WS-PROG-NAME
END-EVALUATE
* Dynamic call -- program loaded at runtime
CALL WS-PROG-NAME USING WS-TRANSACTION-DATA
WS-RESULT-DATA
The key difference from static calls is visible in the JCL: each subprogram is linked into its own load module, and the runtime searches STEPLIB to find and load the requested program.
See: - example-02-dynamic-call.cob -- Main program - example-02-subprog-a.cob -- Deposit/withdrawal handler - example-02-subprog-b.cob -- Inquiry handler - example-02-dynamic-call.jcl -- Complete JCL
17.22 Expected Output
Example 1: Static Call Output
=========================================
PAYROLL CALCULATION RESULTS
=========================================
Employee ID: E10045
Employee Name: JOHNSON, ROBERT M.
Gross Pay: 4,583.33
Tax Amount: 1,008.33
Net Pay: 3,575.00
=========================================
Example 3: Parameter Passing Modes Output
=============================================
PARAMETER PASSING MODES DEMONSTRATION
=============================================
--- BEFORE CALL ---
BY REFERENCE field: [REFERENCE-ORIGINAL ]
BY CONTENT field: [CONTENT-ORIGINAL ]
BY VALUE field: [00000042]
(Inside subprogram SUBPARM)
Received BY REFERENCE: [REFERENCE-ORIGINAL ]
Received BY CONTENT: [CONTENT-ORIGINAL ]
Received BY VALUE: [00000042]
After modifications inside subprogram:
REF field is now: [MODIFIED-BY-SUB ]
CONT field is now: [MODIFIED-CONTENT ]
VAL field is now: [00000142]
--- AFTER CALL ---
BY REFERENCE field: [MODIFIED-BY-SUB ] <-- CHANGED
BY CONTENT field: [CONTENT-ORIGINAL ] <-- UNCHANGED
BY VALUE field: [00000042] <-- UNCHANGED
Message from sub: [Subprogram completed all modifications]
Example 5: CANCEL Statement Output
=============================================
CANCEL STATEMENT DEMONSTRATION
=============================================
--- CALLS WITHOUT CANCEL ---
(Counter in subprogram persists between calls)
Call #1: subprogram counter = 000001
Call #2: subprogram counter = 000002
Call #3: subprogram counter = 000003
--- CANCEL issued ---
Subprogram COUNTPGM has been canceled.
--- CALLS AFTER CANCEL ---
(Counter resets because WORKING-STORAGE was
reinitialized when the program was reloaded)
Call #4: subprogram counter = 000001
Call #5: subprogram counter = 000002
Call #6: subprogram counter = 000003
--- ONE MORE CALL WITHOUT CANCEL ---
Call #7: subprogram counter = 000004
Notice: After CANCEL, the counter reset to 1.
Without CANCEL, it continued incrementing.
=============================================
Summary
Subprograms are the foundation of modular COBOL programming. The CALL statement, combined with the LINKAGE SECTION and PROCEDURE DIVISION USING clause, provides a clean mechanism for decomposing large programs into manageable, reusable components.
Key concepts from this chapter:
- Static calls resolve at link time, producing larger but faster load modules
- Dynamic calls resolve at runtime, offering flexibility and memory efficiency
- BY REFERENCE shares memory; BY CONTENT protects the caller; BY VALUE passes stack values
- RETURN-CODE and RETURNING provide status communication
- GOBACK (not STOP RUN) is the correct exit from a subprogram
- CANCEL releases dynamically loaded programs and resets their WORKING-STORAGE
- Nested programs provide contained, single-compilation-unit modularity
- Copybooks for parameter definitions ensure caller-callee alignment
- Good interface design -- return codes, error messages, grouped parameters -- is the key to successful modular architecture
Mastering these concepts will enable you to write COBOL systems that are maintainable, testable, and built from reusable components -- exactly the qualities that have kept COBOL systems running reliably for decades.