|Document revision date: 30 March 2001|
Implicit arguments allocated by the called procedure are kept in local static storage.
These implicit arguments are usually used to keep track of resources (using resource allocating procedures) and shorten the explicit argument list. However, the use of implicit inputs by non-resource-allocating procedures can lead to unexpected results. For example, assume that procedure A is to leave information for a companion procedure B. This would result in B having both explicit inputs (from its caller) and implicit inputs (from A's storage). Next, consider that a calling program calls A, then calls procedure X, and finally calls B. For the calling program to get correct results from B, it must know that X (and any procedure that X calls) did not make a call to A, because such a call would change the implicit inputs A leaves for B.
Because one of the objectives of modular programming is to permit
procedures to be combined arbitrarily without needing to understand
each other's internal workings, Compaq does not recommend using
implicit arguments. The same problems can occur with any
non-resource-allocating procedure that leaves results for itself as
future implicit arguments.
2.2.3 How to Avoid Using Implicit Arguments
Procedures that do not allocate resources can be written in the following three ways to avoid the implicit argument problems described in Section 2.2.2:
Often, non-resource-allocating procedures can be combined into a single procedure that returns all information explicitly in a single call.
Compare Example 2-1 with Example 2-2 to see the effects of combining procedures to avoid the use of implicit arguments.
|Example 2-1 FORTRAN Program Showing the Improper Use of Implicit Arguments|
!+ ! This program demonstrates a situation where ! the input of a procedure depends on the output ! of a previously called procedure. !- REAL*4 X, Y, RESULT X = 1 Y = 1 !+ ! Call the procedure that writes into a common data area. !- CALL SUM_SQUARES (X, Y) !+ ! Call the procedure that reads from the common data area. !- CALL GET_SQRT (RESULT) !+ ! Print the result obtained. !- WRITE (6,10) X, Y, RESULT 10 FORMAT(1X, 'SQRT(', F6.2, '**2 + ', F6.2, '**2) =',F6.2) STOP END !+ ! This procedure sums the squares of its two inputs and ! places the result in a common area, for use by some ! other procedure. !- SUBROUTINE SUM_SQUARES (A, B) COMMON /INTERNAL_STORAGE/ TEMP_RESULT TEMP_RESULT = (A ** 2) + (B ** 2) RETURN END !+ ! This procedure calculates the square root of whatever ! number is in the common area. !- SUBROUTINE GET_SQRT (C) COMMON /INTERNAL_STORAGE/ TEMP_RESULT C = SQRT (TEMP_RESULT) RETURN END
Another way to combine several procedures into one call is to let the calling program gain control at a critical point in your procedure's execution. For this to happen, your procedure must specify an action routine argument that is called during execution. Therefore, your procedure can execute twice, before and after the action routine, with no implicit inputs. The OPEN statements in BASIC, FORTRAN, and Pascal use this technique by permitting the user to supply a user-action routine.
To keep the calling program from having to provide implicit inputs for its action routine, your procedure should also provide another argument that is passed to the action routine. The calling program uses the following calling sequence to invoke your procedure:
CALL my-proc (... ,action-routine ,user-arg)
Then your procedure invokes the action routine as follows:
CALL action-routine (... ,user-arg)
For information on writing user-action routines, see Section 3.1.4.
|Example 2-2 FORTRAN Program Combining Procedures to Avoid Implicit Arguments|
!+ ! This procedure shows the subroutines called in ! the previous example combined into a single subroutine ! that eliminates the use of COMMON. !- REAL*4 X, Y, RESULT X = 1 Y = 1 !+ ! Call the new procedure. !- CALL DO_IT_ALL (X, Y, RESULT) WRITE (6,10) X, Y, RESULT 10 FORMAT (1X, 'SQRT (', F6.2, '**2 + ', F6.2, '**2) = ',F6.2) STOP END !+ ! This procedure calculates the square root of the sum of ! the squares of its first two arguments, and returns the ! result in the third argument. It combines the functions ! provided by the SUM_SQUARES and GET_SQRT ! procedures and eliminates the use of COMMON. !- SUBROUTINE DO_IT_ALL (A, B, C) C = SQRT ((A ** 2) + (B ** 2)) RETURN END
You can make the calling program responsible for retaining information from one procedure activation to another. There are three ways to do this:
Figure 2-3 shows a calling program that has responsibility for explicitly indicating the storage to be used by the called procedure.
Figure 2-3 Designating Storage Responsibility to the Caller
This method causes the calling program to allocate all storage needed and pass the address of the storage as an explicit argument on each call.
For example, the library procedure MTH$RANDOM requires that the calling program allocate storage for the longword seed and pass its address on each call. MTH$RANDOM takes the seed as input and computes the next random number sequence from the current seed value. MTH$RANDOM returns a random number between 0 and 1 and updates the longword seed passed by the calling program. This ensures that the procedure will generate a different value on the next call.
The next two sections describe interface techniques that permit storage size to change without affecting the interface with the calling program.
In this method, the calling program allocates only a longword pointer to the dynamic heap storage to be allocated by your procedure. It then passes the address of the longword as an explicit argument. The following two interface techniques can be used to indicate that storage is to be initialized:
Regardless of the method used to indicate storage allocation and initialization, you must also provide a way to indicate storage deallocation. You can do this by using either a separate argument or separate entry point.
For example, the procedure LIB$INIT_TIMER, which gets times and counts from the operating system, uses a single optional argument handle-adr to determine where these values are to be stored. The handle-adr argument is the address of a longword pointing to a block of storage that contains the values of times and counts:
LIB$FREE_TIMER deallocates the block of dynamic heap storage allocated by a previous call to LIB$INIT_TIMER. The handle-adr argument to LIB$FREE_TIMER is the address of a longword that points to a block of dynamic heap storage where times and counts have been stored. That storage is returned to free storage by calling LIB$FREE_VM.
In this method, the calling program passes a processwide identifying value to identify implicit results produced on previous calls, which will be implicit inputs on this call. Any calling program can use the processwide identifier. Examples include BASIC or FORTRAN logical unit numbers and OpenVMS system services I/O channel numbers.
Processwide identifiers are a resource. Modular programming techniques require that all resources allocated by a procedure be allocated by calling a resource-allocating procedure. This prevents conflicts because a single procedure can keep track of multiple allocations to more than one procedure or procedure activation. Therefore, if you use the method described in this section, you will also have to write a resource-allocating procedure to control the resource. If you write a resource-allocating procedure, it is recommended that you place it in an object module library so that other programmers can use it.
The library procedures LIB$GET_LUN and LIB$FREE_LUN allocate and
deallocate FORTRAN and BASIC logical unit numbers outside the range
normally specified in user programs, that is, outside the range 0 to 99.
2.2.4 Order of Arguments
Note that optional arguments follow required arguments. Therefore, when the calling program omits the optional arguments, the actual argument list passed to the procedure is shortened.
The called procedure accesses the required arguments from left to
right, beginning with the first argument. The only exceptions are
procedures that return a large function value of known size. In this
case, the calling program uses the first argument to specify where the
function value is to be stored, and the other arguments are shifted
right one position. (For more information, refer to the OpenVMS Calling Standard.)
2.2.5 Using Optional Arguments
An optional argument is one that the calling program can omit. The calling program indicates the omission by passing argument list entries containing zero. For a trailing optional argument, the calling program can pass a shortened list or a zero argument list entry.
A zero argument list entry is simply a zero passed to the procedure by value. For example, if we call a procedure called GRA_CUBE and omit an optional argument C, the calling sequence from BASIC would be as follows:
15 CALL GRA_CUBE(A, B, 0 BY VALUE)
In this call, 0 BY VALUE is the zero argument list entry.
Most OpenVMS system services, unlike the run-time library procedures, cannot accept a shortened argument list. Omitted arguments must always be indicated with a zero argument list entry. For arguments passed by value, there is no distinction between passing a zero value and passing a zero argument list entry.
On VAX systems, Compaq recommends that you do not use JSB2 entry points in procedures that will be contained in a procedure library. Procedures that can be invoked only by JSB instructions are not callable by high-level languages. If a procedure does use a JSB entry point, it must also provide an equivalent call entry point to maintain language independence. The call entry point must be provided because JSB instructions are only available in VAX MACRO and VAX BLISS-32.
If you provide a JSB entry point for your procedure, the name of the JSB entry point is the same as the name of the procedure, except that it ends in _Rn. The n indicates the highest register modified or used as an input argument.
For example, the JSB entry point of the run-time library procedure LIB$ANALYZE_SDESC is LIB$ANALYZE_SDESC_R2.
2 JSB is a MACRO instruction that means jump to subroutine.
The system resources available to you are limited by your account
quotas and by the amount of available resources on the system.
Efficient use of system resources makes more resources available for
2.4.1 Choosing a Storage Type
There are three types of storage: stack, heap, and static. The three
forms of storage differ in the method and duration of allocation, that
is, how long that storage is in use.
126.96.36.199 Stack Storage
A procedure dynamically allocates stack storage on the process stack at run time, as needed. To allocate stack storage, the procedure moves the stack pointer up by decreasing its value. Note that stack storage is not initialized to zero because the stack is created once and reused many times for subsequent stack frames.
The procedure deallocates stack storage by moving the stack pointer
down (increasing its value) when that procedure returns control to the
calling program. Stack storage exists only for the duration of the
procedure activation that creates it.
188.8.131.52 Heap Storage
To allocate heap storage, your procedure calls a system routine such as the Run-Time Library procedure LIB$GET_VM or the system service $EXPREG. The call to the system routine can be within the procedure itself, or you can use a general resource-allocating procedure to centralize your resource allocations.
Heap storage is deallocated---that is, returned to the processwide pool---by calling LIB$FREE_VM. The system service $CNTREG cannot be used to deallocate heap storage.
Figure 2-4 shows how the different types of storage are used.
The type of storage to be used can be determined by the duration or quantity of the storage. Any storage that is of long duration and unknown quantity (at compile time) should be heap storage. Storage of short duration (during the current invocation of the procedure) should be stack storage. Storage of long duration that is needed in only one instance should be static storage.
Figure 2-4 Use of Storage Types
At link time, the linker collects storage in similar PSECTs into a
single image section. The initial contents of this storage are
specified in the source program. The OpenVMS operating system
initializes any noninitialized static storage to zero. On calls to a
procedure after initialization, the static storage has the same
allocation and the contents left from the previous call.
184.108.40.206 Avoiding Use of Static Storage
The following are several disadvantages to using static storage:
|Example 2-3 Static Storage and AST Reentrancy|
10 !+ ! Program to demonstrate corruption ! of static storage due to ASTs. !- DECLARE LONG CURRENT_NUMBER !+ ! Enable CTRL/C AST handling. !- ON ERROR GOTO 19000 X% = CTRLC !+ ! Increment the number and print the ! current value. When the number ! reaches 1000, exit. !- FOR CURRENT_NUMBER = 1% TO 1000% 100 PRINT CURRENT_NUMBER; NEXT CURRENT_NUMBER GOTO 32767 19000 !+ ! Error-handling routine. If this routine is ! entered due to a CTRL/C ! AST, corrupt CURRENT_NUMBER by setting it to -1. !- IF ERR = 28 THEN CURRENT_NUMBER = -1% RESUME 100 32767 END
Table 2-1 summarizes storage available to the programmer in various language procedures.
|Ada||Constants and fixed-size objects contained in library packages||Local subprogram and task variables||Dynamically sized objects in library packages and objects created by allocators|
|BASIC||All COMMON and MAP data storage||Local variables||Dynamic strings|
|Most arrays||Executable DIMENSION statement|
|BLISS||OWN and GLOBAL||STACK LOCAL||By calling LIB$GET_VM|
|C||Objects declared with external or static internal linkage||Objects declared inside a function with "automatic" linkage||By calling malloc, calloc, or realloc|
|COBOL||All data storage||Not applicable||By calling LIB$GET_VM|
|DIBOL||All RECORD, COMMON, and LITERAL data storage||Not applicable||Not applicable|
|VAX FORTRAN||All data storage||Not applicable||By calling LIB$GET_VM 1|
|Assembly language||Block storage||Decrementing stack pointer||By calling LIB$GET_VM|
|Pascal||All program or module level storage||PROCEDURE and FUNCTION local||By calling NEW 2|
|PL/I||STATIC||AUTOMATIC||ALLOCATE statement (BASED) 3|
|RPG II||All data storage||Not applicable||By calling LIB$GET_VM|
|privacy and legal statement|