Michael Coughlan
Page 49
program to invoke another and to pass data to it. In many programming languages, the procedure or function call serves this purpose. In COBOL, the CALL verb is used to invoke one program from another.
The CALL Verb
The CALL verb is used to transfer control (program execution) to an external, independently compiled subprogram or a contained subprogram. When the subprogram terminates, control reverts to the statement after CALL. The metalanguage for the CALL verb is given in Figure 16-1.
Figure 16-1. Metalanguage for the CALL verb
Some notes relating to the metalanguage follow:
• BY REFERENCE and BY CONTENT are parameter-passing mechanisms. BY REFERENCE is the
default and so is sometimes omitted (hence the square brackets).
• If the called program has not been linked (does not exist in the executable image), the
statements following ON EXCEPTION execute. Otherwise, the program terminates abnormally.
• If the CALL passes parameters, then the called subprogram must have a USING phrase after the
PROCEDURE DIVISION header and a LINKAGE SECTION to describe the parameters that are passed.
• The CALL statement may only have a USING phrase if the PROCEDURE DIVISION header of the
called subprogram also has a USING phrase.
• Both USING phrases must have the same number of parameters.
• Unlike some languages, COBOL does not check the type of the parameters passed to a called
subprogram. It is your responsibility to make sure that only parameters of the correct type and
size are passed.
•
As shown in Figure 16-2, the parameters passed from the calling program to the called
subprogram correspond by position, not by name. That is, the first parameter in the USING phrase
of the CALL corresponds to the first parameter in the USING phase of the called program, and so on.
400
Chapter 16 ■ Creating Large SyStemS
Figure 16-2. CALL parameters correspond by position not name
• Implementers often extend CALL by introducing BY VALUE parameter passing and by including
a GIVING phrase. These are nonstandard extensions.
Parameter-Passing Mechanisms
As you can see from the metalanguage in Figure 16-1, the CALL verb has two parameter-passing mechanisms: BY REFERENCE and BY CONTENT. You should use BY REFERENCE only when the called subprogram needs to pass data back to the caller. You should always use BY CONTENT when data needs to be passed to, but not received from, the called program.
It is a principle of good program design that you should not expose a subprogram to more data than it needs in order to work. If you pass your data BY REFERENCE, the possibility exists that it may be corrupted by the called subprogram. When you pass data BY CONTENT, there is no possibility of that happening.
Figure 16-3 and Figure 16-4 show how each of these mechanisms works.
Figure 16-3. The CALL..BY REFERENCE parameter-passing mechanism
Figure 16-4. The CALL..BY CONTENT parameter-passing mechanism
401
Chapter 16 ■ Creating Large SyStemS
CALL..BY REFERENCE
When data is passed BY REFERENCE, the address of the data item is supplied to the called subprogram (see Figure 16-3).
Therefore, any changes made to the data item in the subprogram are also made to the data item in the main program, because both items refer to the same memory location.
CALL..BY CONTENT
When a parameter is passed BY CONTENT, a copy of the data item is made, and the address of the copy is supplied to subprogram (see Figure 16-4). Any changes made to the data item in the subprogram affect only the copy.
Subprograms
I have said that a subprogram is just a program that is invoked by another program rather than by the user/operator.
In most ways, this is true. A subprogram may have all the divisions, sections, and paragraphs that a program has, but subprograms may also have additional sections and phrases. In addition, because it is contained within the source text of a containing program, a contained subprogram is not quite the same as an external subprogram (one whose source code is in a document separate from the main program source).
Example 16-1 is a template for a subprogram that shows the additional sections and clauses in bold. The
subprogram in Example 16-1 might be invoked with a CALL statement such as this:
CALL "ValidateCheckDigit" USING BY CONTENT StudentId
BY REFERENCE CKD-Result
Note that the CALL uses a literal value to identify the subprogram being invoked and that therefore the name is enclosed in quotes. This is the usual way a subprogram is invoked, because when you write a program, you usually know which subprogram you want to call. If you wanted to choose dynamically which program to call, you would use a data item to hold the program name. For instance:
DISPLAY "Enter the subprogram name - " WITH NO ADVANCING
ACCEPT SubprogramName
CALL SubprogramName
Example 16-1. Subprogram Template
IDENTIFICATION DIVISION.
PROGRAM-ID. ValidateCheckDigit IS INITIAL.
DATA DIVISION.
WORKING-STORAGE SECTION.
: : : : : : :
: : : : : : :
LINKAGE SECTION.
01 NumToValidate PIC 9(7).
01 Result PIC 99.
PROCEDURE DIVISION USING NumToValidate, Result.
Begin.
: : : : : : :
: : : : : : :
EXIT PROGRAM.
402
Chapter 16 ■ Creating Large SyStemS
Note that the name given in the CALL statement (ValidateCheckDigit) corresponds to the name given in the
PROGRAM-ID of the called program. The main purpose of the PROGRAM-ID clause is to identify programs in a run-unit (the group of programs that have been compiled and linked into one executable image). The CALL transfers control from one program in the run-unit to another.
In this template, the IS INITIAL clause is attached to the PROGRAM-ID. I discuss the IS INITIAL clause and the problem of state memory, which it solves, in the next section.
This template uses a LINKAGE SECTION. A LINKAGE SECTION (which comes after the WORKING-STORAGE SECTION)
is always required if parameters are passed to a subprogram. The LINKAGE SECTION is used to define the parameters and reserve storage for them. If a LINKAGE SECTION is required, then the subprogram’s PROCEDURE DIVISION header requires a USING phrase. The USING phrase matches the actual parameters of the CALL (by position in the parameter list) to the formal parameters in the subprogram.
■ Note you probably know what i mean by actual parameters and formal parameters; but in case you don’t, here is an explanation. any useful subprogram is likely to be called from a number of different places and for different purposes.
For instance, the check-digit validation subprogram might be called by various programs to validate NewStudentId, OldStudentId, GraduatedStudentId, TransferStudentId, or even (because it validates any seven-digit number) StockId.
these data-item names are the names of the actual parameters that are passed to the subprogram. When you write the subprogram, you don’t always know the names of the data items that will be passed as parameters (a maintenance programmer, for instance, might write a new routine and call your subprogram); and in any case, there are multiple names—which do you choose? So, the name that you use in the subprogram is a placeholder (or formal parameter) for the actual parameter that is passed to the subprogram.
The EXIT PROGRAM statement in Example 16-1 stops the execution of the subprogram and transfers control
back to the caller. You place the EXIT PROGRAM statement where you would normally place STOP RUN. The difference between STOP RUN and an EXIT PROGRAM statement is that STOP RUN causes the entire run-unit to stop (even if STOPr />
RUN is encountered in a subprogram) instead of just the subprogram.
Contained Subprograms
As I explained earlier, a contained subprogram is a program contained within the source code of another program.
When you use contained subprograms, the END PROGRAM header is required to delimit the scope of each subprogram and to wrap your subprograms within the scope of the main (container) program. The END PROGRAM header has this format: END PROGRAM ProgramIdName.
Example 16-2 shows the ValidateCheckDigit subprogram implemented as a contained subprogram. In this
instance, it is contained within a main program called CheckDigitDrv.
Example 16-2. Outline of a Main Program and Its Contained Subprogram
IDENTIFICATION DIVISION.
PROGRAM-ID. CheckDigitDrv.
: : : : : : :
: : : : : : :
CALL "ValidateCheckDigit" USING BY CONTENT StockId
BY REFERENCE CKD-Result
: : : : : : :
: : : : : : :
403
Chapter 16 ■ Creating Large SyStemS
IDENTIFICATION DIVISION.
PROGRAM-ID. ValidateCheckDigit IS INITIAL.
: : : : : : :
: : : : : : :
PROCEDURE DIVISION USING NumToValidate, Result.
: : : : : : :
: : : : : : :
END PROGRAM ValidateCheckDigit.
END PROGRAM CheckDigitDrv.
Contained Subprograms vs. External Subprograms
I mentioned that contained subprograms are not quite the same as external subprograms. You have already seen one difference: the END PROGRAM header. Another difference is the visibility of data. In an external subprogram, it is obvious that it can’t see data declared in the main program or other subprograms (although this is not entirely true, as you will see when you examine the IS EXTERNAL clause), because it is a separate, independent program. But because the text of a contained subprogram is contained within the text of the main (container) program, you may wonder whether the subprogram can see the data declared in the main program and whether the main program can see the data declared in the subprogram. In COBOL, data declared in a subprogram cannot be seen outside it, and data declared in the main (containing) program cannot be seen in the subprogram, unless … unless what? For the answer, you have to wait for the explanation of the IS GLOBAL clause later in this chapter.
An issue that does not arise in relation to external subprograms but is a burning issue for contained subprograms is invokability. Contained subprograms can be nested: that is, a contained subprogram may itself contain a subprogram. So the question arises, can a nested subprogram be called from anywhere? Or are there restrictions?
Sadly, there are restrictions. A contained subprogram can only be called by its immediate parent (container) program or by a subprogram at the same level. Even this isn’t entirely true; a subprogram can only be called by a subprogram (sibling) at the same level if the called program uses the IS COMMON PROGRAM clause (see the next section) in its PROGRAM-ID.
State Memory and the IS INITIAL Phrase
The first time a subprogram is called, it is in its initial state: all files are closed, and the data items are initialized to their VALUE clauses. The next time the subprogram is called, it remembers its state from the previous call. Any files that were opened are still open, and any data items that were assigned values still contain those values.
Although it can be useful for a subprogram to remember its state from call to call, systems that contain subprograms with state memory are often less reliable and more difficult to debug than those that do not. A subprogram that does not have state memory is predictable, because for the same input value, it produces the same result. Subprograms that have state memory are more difficult to debug because they may produce different results for the same input values.
You can force a subprogram into its initial state each time it is called by including the IS INITIAL clause in the PROGRAM-ID. The metalanguage for the IS INITIAL clause is given in Figure 16-5. Note that INITIAL is only one of the clauses that can be attached to the PROGRAM-ID. IS COMMON PROGRAM may also be applied to a subprogram. I examine the IS COMMON PROGRAM clause in more detail later in the chapter.
Figure 16-5. Metalanguage for the IS COMMON and IS INITIAL clauses
404
Chapter 16 ■ Creating Large SyStemS
Listing 16-1 has a dual purpose. It shows how contained subprograms are created and used, and it demonstrates the difference between a subprogram that has state memory and one that does not. The listing consists of a main program and two subprograms named Steady and Dynamic. Steady is so named because every time you call it
with the same parameter values, it produces the same results. But Dynamic, because it remembers its state from the previous call, produces different results when it is called with the same input values.
Listing 16-1. State Memory Demonstration with Steady and Dynamic
IDENTIFICATION DIVISION.
PROGRAM-ID. Listing16-1.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 Increment PIC 99 VALUE ZERO.
88 EndOfData VALUE ZERO.
PROCEDURE DIVISION.
Begin.
*> Demonstrates the difference between Steady
*> and Dynamic. Entering a zero ends the iteration
DISPLAY "Enter an increment value (0-99) - " WITH NO ADVANCING
ACCEPT Increment
PERFORM UNTIL EndOfData
CALL "Steady" USING BY CONTENT Increment
CALL "Dynamic" USING BY CONTENT Increment
DISPLAY SPACES
DISPLAY "Enter an increment value (0-99) - " WITH NO ADVANCING
ACCEPT Increment
END-PERFORM
STOP RUN.
IDENTIFICATION DIVISION.
PROGRAM-ID. Dynamic.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 RunningTotal PIC 9(5) VALUE ZERO.
01 PrnTotal PIC ZZ,ZZ9.
LINKAGE SECTION.
01 ValueToAdd PIC 99.
PROCEDURE DIVISION USING ValueToAdd.
Begin.
ADD ValueToAdd TO RunningTotal
MOVE RunningTotal TO PrnTotal
DISPLAY "Dynamic total = " PrnTotal
EXIT PROGRAM.
END PROGRAM Dynamic.
IDENTIFICATION DIVISION.
PROGRAM-ID. Steady IS INITIAL.
DATA DIVISION.
405
Chapter 16 ■ Creating Large SyStemS
WORKING-STORAGE SECTION.
01 RunningTotal PIC 9(5) VALUE ZERO.
01 PrnTotal PIC ZZ,ZZ9.
LINKAGE SECTION.
01 ValueToAdd PIC 99.
PROCEDURE DIVISION USING ValueToAdd .
Begin.
ADD ValueToAdd TO RunningTotal
MOVE RunningTotal TO PrnTotal
DISPLAY "Steady total = " PrnTotal
EXIT PROGRAM.
END PROGRAM Steady.
END PROGRAM Listing16-1.
Notice that each time Steady is passed the same value, it produces the same result; but each time Dynamic
is passed the same value, it produces a different result, because it remembers the state of the data items from the previous invocation. Sometimes, such as when you need to keep a running total, you want the subprogram to have state memory. But as a rule, unless you explicitly want a subprogram to remember its state, you should use the IS INITIAL phrase to set the program to its initial state each time it is called.
The CANCEL Verb
A program may need state memory only part of the time. That is, it needs to be reset to its initial state periodically.
In COBOL, you can do this using the CANCEL verb. The metalanguage for the CANCEL verb is given in Figure 16-6.
Figure 16-6. Metalanguage for the CANCEL verb
When the CANCEL command is executed, the m
emory space occupied by the subprogram is freed. If the subprogram is called again, it is in its initial state (all files declared in the subprogram are closed, and all data items are initialized to their VALUE clauses). As shown in Example 16-3, you can use the CANCEL verb to force Dynamic to act like Steady.
Example 16-3. Using the CANCEL Verb to Force Dynamic to Act Like Steady
DISPLAY "First Call"
CALL "Dynamic" USING BY CONTENT 77.
CANCEL "Dynamic"
DISPLAY SPACES
DISPLAY "Second Call"
CALL " Dynamic" USING BY CONTENT 77.
The IS GLOBAL Clause
I noted earlier that data declared in a contained subprogram cannot be seen in the main (containing) program, and data declared in the main program cannot be seen inside a contained subprogram. In general, this is true;
but sometimes you may want to share a data item with a number of contained subprograms. For instance, consider 406
Chapter 16 ■ Creating Large SyStemS
the program fragments in Example 16-4. This program produces a report showing the purchases of new automobiles in the United States. The data is accumulated in a table and then printed.
The program is partitioned into a main program and two subprograms. One subprogram adds the value of each
new car purchase to the appropriate state in the table. The other subprogram prints the report when the new car purchases have been processed. Both subprograms need access to the table. The table cannot be declared local to the subprogram because any local declarations cannot be seen outside the subprogram. So the table must be declared in the outer scope: the main (container) program. The problem then is how to allow the table to be seen by the subprograms.
One approach might be to pass the table through the parameter list. The problem with this approach is that there is a lot of data in the table, and every time AddToStateTotal is called, the table must be passed. A better solution is to make the table visible inside the subprograms. You can do this using the IS GLOBAL clause. Any data item to which the IS GLOBAL clause is attached is visible within the subordinate subprograms.