Chapter 17: Subprograms and the CALL Statement -- Key Takeaways
Chapter Summary
Modular programming is the practice of decomposing a large program into smaller, self-contained units that can be developed, tested, and maintained independently. In COBOL, the primary mechanism for modular programming is the subprogram, invoked from a calling program using the CALL statement. This chapter explored the complete lifecycle of COBOL subprogram interaction: how the calling program packages arguments in the CALL ... USING clause, how the subprogram receives them through the PROCEDURE DIVISION USING clause and the LINKAGE SECTION, and how the three parameter-passing mechanisms -- BY REFERENCE, BY CONTENT, and BY VALUE -- control whether the subprogram can modify the caller's data.
The distinction between static and dynamic calls has significant implications for program loading, memory management, and deployment flexibility. A static call (typically CALL "literal-name") binds the subprogram into the same load module at link-edit time, resulting in a single executable where the subprogram resides in memory for the life of the calling program. A dynamic call (typically CALL identifier or compiler-option-controlled) loads the subprogram into memory at runtime when the CALL executes and can release it with the CANCEL statement, allowing memory to be reclaimed and a fresh copy to be loaded on the next call. Dynamic calls provide deployment flexibility because subprograms can be replaced independently without relinking the caller, but they incur additional overhead for each load operation.
The chapter also covered nested programs, where one or more complete COBOL programs are physically contained within the source code of another program. Nested programs provide a lightweight alternative to separate compilation units when subprograms are tightly coupled to a single caller. The COMMON clause makes a nested program callable by sibling nested programs, and the INITIAL clause ensures that a subprogram is reset to its initial state each time it is called, preventing stale data from one invocation from affecting the next. Finally, we discussed the GOBACK statement as the preferred way to return control from a subprogram, the STOP RUN statement's behavior when issued from a subprogram (it terminates the entire run unit), and the use of RETURN-CODE to communicate status information back to the caller.
Key Concepts
- The CALL statement transfers control from a calling program to a subprogram, optionally passing data items through the USING phrase.
- BY REFERENCE (the default) passes the memory address of the data item, allowing the subprogram to read and modify the caller's original data directly.
- BY CONTENT passes a copy of the data item's value; the subprogram can modify its local copy without affecting the caller's original data.
- BY VALUE passes the data item as a value parameter compatible with C calling conventions, used primarily when calling non-COBOL programs such as C functions or system APIs.
- The LINKAGE SECTION in the subprogram defines the data items that correspond to the parameters passed by the caller; these items describe the layout of memory owned by the caller.
- The PROCEDURE DIVISION USING clause in the subprogram lists the LINKAGE SECTION items that receive the caller's arguments, matched by position (not by name) with the caller's CALL ... USING list.
- Static calls bind the subprogram into the caller's load module at link-edit time, creating a single executable; the subprogram is always in memory when the caller runs.
- Dynamic calls load the subprogram into memory at runtime when the CALL executes, using the load module library (STEPLIB/JOBLIB) to locate the subprogram's executable.
- The CANCEL statement releases a dynamically called subprogram from memory, freeing resources and ensuring that the next CALL loads a fresh copy.
- Nested programs are complete COBOL programs contained within the source code of another program, defined between the caller's last section and its END PROGRAM marker.
- The COMMON clause on a nested program's PROGRAM-ID allows it to be called by sibling nested programs, not just by its direct containing program.
- The INITIAL clause on a nested program's PROGRAM-ID causes the program to be reinitialized to its compiled state each time it is called, resetting all WORKING-STORAGE items.
- GOBACK returns control to the calling program (or to the operating system if issued from the main program) and is the preferred return mechanism for subprograms.
- STOP RUN terminates the entire run unit regardless of which program in the call chain issues it; using STOP RUN in a subprogram terminates the caller and all programs above it.
- RETURN-CODE is a special register that allows a subprogram to pass a numeric status value back to the calling program, commonly used to indicate success (0) or failure (nonzero).
Common Pitfalls
- Mismatching parameter counts or data descriptions: The CALL ... USING list and the PROCEDURE DIVISION USING list must have the same number of items, and the data descriptions must be compatible in size and type. A mismatch causes data corruption or abends (S0C4 on z/OS) that are difficult to diagnose.
- Using BY REFERENCE when BY CONTENT is needed: If a subprogram should not be able to modify the caller's data (for example, a validation routine), pass BY CONTENT. Passing BY REFERENCE allows accidental modification of the caller's data, leading to subtle bugs.
- Calling STOP RUN from a subprogram: STOP RUN terminates the entire run unit, not just the subprogram. This is almost never the intended behavior in a subprogram. Use GOBACK to return control to the caller.
- Forgetting to CANCEL a dynamic subprogram: A dynamically called subprogram retains its WORKING-STORAGE values between calls (unless INITIAL is specified). If the subprogram uses flags or counters that must be reset between invocations, either CANCEL it before the next CALL or code the subprogram to initialize itself.
- Assuming parameter matching is by name: COBOL matches CALL parameters to LINKAGE SECTION items by position in the USING list, not by data name. Reordering parameters on one side without reordering the other causes each parameter to map to the wrong data item.
- Not handling the subprogram-not-found condition: If a dynamic CALL cannot locate the subprogram in the load library, the runtime raises an exception. Use the ON EXCEPTION or ON OVERFLOW phrase on the CALL statement to handle this condition gracefully.
- Modifying LINKAGE SECTION items beyond the passed data length: The LINKAGE SECTION can define items larger than what the caller actually passes. Writing beyond the length of the caller's data corrupts adjacent memory, causing unpredictable abends.
- Passing numeric literals BY REFERENCE: A numeric literal cannot be passed BY REFERENCE because it has no modifiable memory location. Use BY CONTENT for literals, or move the literal to a working-storage item and pass that item.
Quick Reference
* ============================================
* CALLING PROGRAM
* ============================================
IDENTIFICATION DIVISION.
PROGRAM-ID. MAINPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CUST-ID PIC X(10).
01 WS-CUST-NAME PIC X(40).
01 WS-RESULT-CODE PIC 9(04).
PROCEDURE DIVISION.
MAIN-PARA.
* Static call with BY REFERENCE (default)
CALL "CUSTLOOK"
USING WS-CUST-ID
WS-CUST-NAME
WS-RESULT-CODE
END-CALL
* Dynamic call using identifier
MOVE "VALDATE" TO WS-PROG-NAME
CALL WS-PROG-NAME
USING BY CONTENT WS-INPUT-DATE
BY REFERENCE WS-VALID-FLAG
ON EXCEPTION
DISPLAY "SUBPROGRAM NOT FOUND: "
WS-PROG-NAME
END-CALL
* CANCEL to release dynamic subprogram
CANCEL WS-PROG-NAME
* Checking RETURN-CODE after CALL
CALL "CALCUTIL"
USING BY REFERENCE WS-AMOUNT
END-CALL
IF RETURN-CODE NOT = 0
DISPLAY "CALCUTIL FAILED: " RETURN-CODE
END-IF
GOBACK.
* ============================================
* CALLED SUBPROGRAM
* ============================================
IDENTIFICATION DIVISION.
PROGRAM-ID. CUSTLOOK.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-FILE-STATUS PIC XX.
LINKAGE SECTION.
01 LS-CUST-ID PIC X(10).
01 LS-CUST-NAME PIC X(40).
01 LS-RESULT-CODE PIC 9(04).
PROCEDURE DIVISION
USING LS-CUST-ID
LS-CUST-NAME
LS-RESULT-CODE.
MAIN-PARA.
MOVE 0 TO LS-RESULT-CODE
PERFORM LOOKUP-CUSTOMER
GOBACK.
* ============================================
* NESTED PROGRAM EXAMPLE
* ============================================
IDENTIFICATION DIVISION.
PROGRAM-ID. OUTERPROG.
PROCEDURE DIVISION.
MAIN-PARA.
CALL "INNERPROG" USING WS-DATA
GOBACK.
* Nested program begins here
IDENTIFICATION DIVISION.
PROGRAM-ID. INNERPROG INITIAL COMMON.
DATA DIVISION.
LINKAGE SECTION.
01 LS-DATA PIC X(100).
PROCEDURE DIVISION USING LS-DATA.
DISPLAY "INNER PROGRAM CALLED"
GOBACK.
END PROGRAM INNERPROG.
END PROGRAM OUTERPROG.
What's Next
Chapter 18 covers Copybooks and the COPY Statement, which extend modular programming from the procedure level to the data level. You will learn how to define reusable data structures, record layouts, and code fragments in separate copybook members that are included into multiple programs at compile time using the COPY statement. The REPLACING phrase allows a single copybook to be customized for different contexts without maintaining multiple versions. Copybooks are the cornerstone of data standardization in enterprise COBOL shops, ensuring that every program that processes a customer record, transaction record, or file layout uses exactly the same field definitions.