DocumentationEndless PL/I for OpenVMSReference Manual  ·  ESS-PFVRM-001

Chapter 5

Storage Classes

The storage class to which a variable belongs determines whether PL/I allocates its storage at compile time or dynamically at run time. This chapter describes the following classes of variables:

  • Automatic variables, which are allocated storage on activation of the declaring procedure

  • Static variables, which are allocated storage at program activation, and which exist for the duration of the program execution

  • Internal variables, which can be referenced only by the declaring procedure and its dynamic descendants

  • External variables, which can be known to blocks outside the block in which they are declared

  • Based variables, which are allocated storage dynamically under program control at run time, and which are accessed by means of a pointer

  • Controlled variables, which are allocated dynamically under program control at run time, and which are accessed sequentially, as on a stack

  • Defined variables, which are not allocated storage, but instead share with the variable upon which it is defined

Section 5.7, “Dynamically Allocated Variables” describes the mechanisms for dynamically allocating storage. Section 5.9, “Storage Sharing” describes how variables can share physical storage locations.

Note

Both Endless PL/I V3.9 for OpenVMS VAX and Endless PL/I V5.0 for OpenVMS Alpha compilers place an upper limit of 536,870,911 (229-1) bytes as the maximum size of any data object. The OpenVMS operating system may impose stricter limits depending on the storage-class parameters of the operating system and the parameters associated with your user name. For detailed information on limits, consult the system manager of your operating system.

5.1. Automatic Variables

The default storage-class attribute for PL/I variables is AUTOMATIC. PL/I does not allocate storage for an automatic variable until the block that declares it is activated. When the block is deactivated the storage is released. For example:

CALC: BEGIN;
DECLARE TEMP FIXED BINARY (31);
   ⋮
END;

Each time the block labeled CALC is activated, storage is allocated for the variable TEMP. When the END statement is executed, the block is deactivated, and all storage for TEMP and all other automatic variables is released. The value of TEMP becomes undefined.

The storage requirements of an automatic variable are evaluated each time the block is activated. Thus, you can specify the length of an automatic character-string variable as follows:

DECLARE STRING_LENGTH FIXED;
  ⋮
COPY: BEGIN;
DECLARE TEXT CHARACTER(STRING_LENGTH);

When this begin block is activated, the length of TEXT is evaluated. The variable is allocated storage depending on the value of STRING_LENGTH, which must have a valid value.

5.2. Static Variables

A static variable is allocated storage when the program is activated, and it exists for the duration of the program. A variable has the static attribute if you declare it with any of the attributes STATIC, EXTERNAL, GLOBALDEF, or GLOBALREF. In declaring static arrays and strings, you must use restricted expressions. (Note that the EXTERNAL scope attribute implies static storage for variables.)

If a block that declares a static variable is entered more than once during the execution of the program, the value of the static variable remains valid. For example:

UNIQUE_ID: PROCEDURE RETURNS (FIXED BINARY(31));
DECLARE ID STATIC INTERNAL FIXED INITIAL (0);
        ID = ID + 1; /* Increment ID */
        RETURN (ID);
        END;

The function UNIQUE_ID declares the variable ID with the STATIC attribute and specifies an initial value of 0 for it. The variable is initialized to this value when the program is activated. The storage for the variable is preserved, and the function returns a different integer value each time it is referenced.

A variable with the STATIC attribute can also have external scope; that is, its definition and value can be accessed by any other procedure that declares it with the STATIC and EXTERNAL attributes.

5.3. Internal Variables

An internal variable is known only within the block in which it is defined and within all contained blocks. By default, PL/I gives all variables the INTERNAL attribute with the exception of data with the FILE and CONDITION attributes.

5.4. External Variables

An external variable provides a way for external procedures to share common data. All declarations that refer to an external variable must also declare it with the EXTERNAL attribute (or with an attribute that implies EXTERNAL) and with identical data type attributes. You can abbreviate the EXTERNAL keyword to EXT. The following example and Figure 5.1, “External Variables” shows how procedures can use external variables:

APPLIC: PROCEDURE OPTIONS (MAIN);
DECLARE FLAGS BIT (64) ALIGNED EXTERNAL;
   ⋮
   CALL READY;

READY: PROCEDURE;
DECLARE FLAGS BIT (64) ALIGNED EXTERNAL;
Figure 5.1. External Variables
External Variables

The OpenVMS Linker allows more control over the definition and allocation of external variables than does PL/I. With the GLOBALDEF attribute, you can define the allocation and initialization of an external variable in a single module. Other PL/I modules can then declare the variable with the GLOBALREF attribute and with no INITIAL attribute.

Further control is provided by the VALUE attribute, which can be used in conjunction with GLOBALDEF and GLOBALREF. A variable declared in this way is a constant whose value is used immediately in instructions generated by the compiler.

The EXTERNAL attribute is implied by the FILE, GLOBALDEF, GLOBALREF, and CONDITION attributes, and also by declarations of entry constants (that is, declarations that contain the ENTRY attribute but not the VARIABLE attribute). For variables, the EXTERNAL attribute implies the STATIC attribute.

The following rules apply to the use of external names:

  • The EXTERNAL attribute directly conflicts with the AUTOMATIC, BASED, DEFINED, and INTERNAL attributes.

  • The EXTERNAL attribute cannot be applied to minor structures, members of structures, parameters, or descriptions in an ENTRY or RETURNS attribute.

  • The EXTERNAL attribute is invalid for variables that are the parameters of a procedure.

  • If a variable is declared as EXTERNAL STATIC INITIAL, all blocks that declare the variable must initialize the variable with the same value.

  • If you declare a file constant or file variable explicitly or implicitly as EXTERNAL, you must use identical attributes, including ENVIRONMENT attributes, in all blocks that declare the file.

5.5. Based Variables

A based variable is a variable that describes a data type associated with storage that will be accessed through a pointer or offset value. PL/I does not allocate any storage for a based variable. Instead, you must explicitly allocate storage.

When you declare a based variable, you provide PL/I with a description of the data that will be accessed by the variable. The actual data must be referenced by a pointer that contains the address of the data storage location. See Section 2.2.5, “BASED Attribute” for information about the BASED attribute. The following example shows a declaration of a based variable:

DECLARE BUFFER CHARACTER(80) BASED (BUF_PTR),
        LINE CHARACTER(80),
        BUF_PTR POINTER;

BUF_PTR = ADDR(LINE);

The declaration of the variable BUFFER does not allocate any storage for it. Rather, PL/I associates the declaration of the variable with the pointer variable BUF_PTR. During the execution of the program, the value of the pointer variable is set to the location (address) in storage of the variable LINE by means of the ADDR built-in function. This effectively associates the description of BUFFER with the actual data value of LINE.

You can associate a based variable with a storage location by using the ADDR built-in function, as in the preceding example; by using the ALLOCATE statement; by using a locator-qualified reference to the based variable; by using the SET option of the READ statement; or by explicit allocation within an area.

The following sections cover these topics:

  • Data types used with based variables: pointers, areas, and offsets

  • Allocation in areas

  • Mechanisms for referring to based variables and for obtaining pointer values to them

  • Based variables and dynamic storage allocation

  • The ADDR built-in function

  • Data type matching for based variables

  • Examples of based variables in use, including allocation in areas

5.5.1. Data Types Used with Based Variables

The data types most commonly associated with based variables are pointers, areas, and offsets.

A pointer is a variable whose value represents the location in memory of another variable or data item. Pointers are used to access based variables and buffers allocated by the system as a result of the SET option of the READ and ALLOCATE statements.

Areas are regions of storage in which based variables can be allocated and freed. The use of areas can simplify and speed operations involving large or numerous based variables.

An offset is a value indicating the location of a based variable relative to the beginning of an area.

5.5.2. Allocation in Areas

PL/I supports storage management in areas (see Section 3.10, “Area Data” for a description of area data). If you use the ALLOCATE statement with an area (either implied or explicitly specified), you can cause the allocation of storage to be performed in that area, instead of in the general memory pool for based and controlled storage.

Storage management in areas has a number of uses, including the following:

  • To allow an area to be moved to different addresses without invalidating its data.

  • To allocate storage that can be freed all at once with low overhead, by allocating variables in an area and then emptying the areas with the EMPTY built-in function rather than freeing the generations one at a time.

  • To allocate storage that can be rolled back, by allocating variables in an area and making periodic assignment of the area to a backup area.

    If it is found that some operations need to be rolled back, the backup area can then be copied back into the current area (or they can be swapped). Note that when areas are assigned to each other, all offsets into the old area are valid for the new area as well.

  • To use areas to overlay shared memory sections, which can be mapped into different address ranges in different processes.

    Note that PL/I will signal the AREA condition if allocation or freeing operations are attempted on such an area from multiple processors simultaneously. (This mechanism is intended only as a debugging aid. If you are going to use shared areas, you need to provide the synchronization for the shared data, typically by using the OpenVMS lock manager or run-time library (RTL) routines such as LIB$BBSSI and LIB$BBCCI that provide multiprocessor interlocking capability.)

You allocate storage in areas with the IN and SET options on the ALLOCATE statement, the AREA and OFFSET data attributes, and the EMPTY built-in function.

The IN option on the ALLOCATE statement takes a reference to an area. If the IN option is not specified, the area is implied from the SET option if the SET option specifies an offset variable with a base area. The SET option itself can be implied from the base variable. Whether an area is specified explicitly or implied, the allocation is performed in that area instead of in the available memory pool for based and controlled storage. If an error is detected in the process, the AREA condition is raised.

PL/I provides full support for allocation in areas as specified in the ANSI full PL/I language standard. In addition, it provides extensions to full PL/I, which enhance the usefulness of areas or provide for improved compatibility with other implementations of PL/I. These extensions are as follows:

  • Area control information is stored in the form of offsets so that an area can be moved to different addresses and still be correct (see Section 3.6, “Offset Data” for a description of offset data). As a result, you can assign areas as members of structures as long as their extents are identical. Note, however, that if such an assignment is performed, the entire area will be copied rather than just the extent. The position independence of the area control information also allows an area to be written to secondary storage and retrieved by another program at a different address. You must ensure that any data allocated in the area is position independent, by restricting locator values stored in the area to offsets specifying that area as a base.

  • You can assign areas of differing sizes to each other directly (that is, not as members of structures). The AREA condition will be raised if the target area is not large enough to hold the extent of the source area.

  • If you transmit an area by itself (as opposed to a member of an aggregate) with a WRITE or REWRITE statement, only the current extent of the area is transmitted (as in the case of varying-length character strings). You can transmit the entire area by using the SCALARVARYING ENVIRONMENT option.

  • The base area in an ALLOCATE statement need not match the base area of the offset. However, if they do match, then one area must contain the other.

  • A normal return from an AREA condition due to a full area during an allocation attempt will result in another allocation attempt. An infinite loop will occur if the problem is not corrected, because the area reference is not reevaluated before the retry is attempted after a normal return. The ON-unit must correct the condition by deallocating storage in the area, or by using the EMPTY built-in function.

  • The control information for an area is stored inside the area. The control information occupies at least 24 to 31 bytes for header information, plus space for linking unused portions of the area. The number of links needed to link unused portions of the area depends on how fragmented the area is. As a result, there are slightly fewer bytes available for the allocation of user variables in an area than the number of bytes the area is declared with.

  • Area variables are not initially empty. They must be explicitly initialized. For example:

    A = EMPTY();

    The initialization can be in an INITIAL clause of the declaration; for example:

    DECLARE A AREA(100) STATIC INITIAL(EMPTY());

    In both examples, the EMPTY built-in function returns an empty area value.

Note that the last three items in this list are features that differ in some other implementations of PL/I.

For examples showing allocation in areas, see Section 5.5.7, “Examples of Based Variables”.

5.5.3. Referring to Based Variables

A reference to a based variable (except in an ALLOCATE statement) must specify a pointer or offset reference designating the storage to be accessed. This qualifying pointer or offset reference can be implicit, if it is specified with the BASED attribute, or explicit, if the based variable reference is prefixed with a locator qualifier. A complete based variable reference (with the locator qualifier) has the following form:

qualifying-reference -> based-reference

Whether explicit or implicit, the qualifying reference must be to a pointer variable, a pointer-valued function, or an offset variable declared with a base area. The qualifying reference is evaluated each time the complete reference is evaluated and must yield a valid pointer value. If the qualifying reference is to an offset variable, the offset value is converted to a pointer using the base area specified in the offset variable's declaration.

You can use both implicit and explicit qualifications with the same based variable; the explicit qualifier overrides the implicit one. For example:

DECLARE X FIXED BIN BASED(P),
    P POINTER,
    (A,B) FIXED BIN;
P = ADDR(A);
X = ADDR(B)->X;

In the second assignment statement, the reference to X on the left-hand side of the assignment has the implicit qualifier P, which is the address of the variable A. The reference to X on the right-hand side is explicitly qualified with the address of another variable, B. This assigns the value of B to the variable A.

In PL/I, you can obtain a valid pointer value in any of the following ways:

  • Through the SET option of the ALLOCATE statement

  • From a user-provided storage allocation routine

  • Through the SET option of the READ statement

  • From applying the ADDR built-in function to an addressable variable

  • By converting an offset value to a pointer value

A pointer value is valid only as long as the storage to which it applies remains allocated. Moreover, a pointer obtained by the application of ADDR to a parameter or an automatic variable is valid only as long as the parameter's procedure invocation exists, even though the storage pointed to may exist longer.

The NULL built-in function returns a null pointer value that can be assigned to pointer and offset variables, but that is not valid for qualifying a based variable reference.

5.5.4. Based Variables and Dynamic Storage Allocation

These subsections discuss the dynamic allocation of storage by the ALLOCATE statement and the READ SET statement.

Using the ALLOCATE Statement

Each time it is executed, the ALLOCATE statement allocates storage for a based variable and, optionally, sets a pointer or offset variable to the location of the storage in memory. The storage allocated can also be assigned values if the variable is declared with the INITIAL attribute. For example:

DECLARE LIST (10) FIXED BINARY BASED, ❶
        (LIST_PTR_A, LIST_PTR_B) POINTER;

ALLOCATE LIST SET (LIST_PTR_A); ❷
ALLOCATE LIST SET (LIST_PTR_B); ❸

LIST_PTR_A -> LIST(1) = 10; ❹
LIST_PTR_B -> LIST(1) = 15;

The numbered items in this example are shown in Figure 5.2, “Using the ALLOCATE Statement”.

As you can see in this example, the array LIST is declared with the BASED attribute; however, the declaration does not reserve storage for this variable. Instead, the ALLOCATE statements allocate storage for the variable and set the pointers LIST_PTR_A and LIST_PTR_B to the storage locations. LIST_PTR_A and LIST_PTR_B must both be declared with the POINTER attribute.

In references, the different allocations of LIST can then be distinguished (unless the pointers are assigned new values) by locator qualifiers that identify the specific allocation of LIST.

The phrase LIST_PTR_A-> is a locator qualifier; it specifies the pointer that locates an allocation of storage for the variable. In this example, the first element of the storage pointed to by LIST_PTR_A is assigned the value 10. The first element of the storage pointed to by LIST_PTR_B is assigned the value 15.

Figure 5.2. Using the ALLOCATE Statement
Using the ALLOCATE Statement

Any extent expressions in the based variable declaration are evaluated each time the variable is allocated or referenced. Therefore, based variables can be used for data aggregates whose size depends on input data. Here is an example of dynamically allocating a matrix that will be accessed by several external procedures:

DECLARE 1 MATRIX_CONTROL_BLOCK STATIC EXTERNAL,
          2 MATRIX_POINTER POINTER,
          2 (ROW_SIZE,COL_SIZE) FIXED BINARY;

DECLARE 1 MATRIX(ROW_SIZE,COL_SIZE)
          BASED(MATRIX_POINTER);

GET LIST(ROW_SIZE,COL_SIZE);
ALLOCATE MATRIX;

The SET Option of the READ Statement

When you use the READ statement with a based variable, you do not have to define storage areas within your program to buffer records for I/O operations. If you specify the SET option on the READ statement, the READ statement places an input record in a system buffer and sets a pointer variable to the location of this buffer. For example:

DECLARE REC_PTR POINTER,
        NEW_BALANCE FIXED DECIMAL (6,2),
        INFILE FILE RECORD INPUT SEQUENTIAL;
DECLARE 1 RECORD_LAYOUT BASED (REC_PTR),
          2 NAME CHARACTER (15),
          2 AMOUNT PICTURE '999V99',
          2 BALANCE FIXED DECIMAL (6,2);
   .
   .
   .
READ FILE (INFILE) SET (REC_PTR) ;
   .
   .
   .
REC_PTR->BALANCE = NEW_BALANCE;
REWRITE FILE (INFILE);

In this example, the structure defined to describe the records in a file is declared with the BASED attribute; the declaration does not reserve storage for this structure. When the READ statement is executed, the record is read into a system buffer and the pointer REC_PTR is set to its location.

When you use the SET option with the READ statement, a subsequent REWRITE statement need not specify the record to be rewritten. PL/I rewrites the record indicated by the pointer variable specified in the READ statement.

Figure 5.3, “Using the READ Statement with a Based Variable” shows this example.

Figure 5.3. Using the READ Statement with a Based Variable
Using the READ Statement with a Based Variable

5.5.5. Using the ADDR Built-in Function

The ADDR built-in function returns the storage location of a variable. You can use it to associate the storage occupied by a variable with the description of a based variable. For example:

DECLARE A FIXED BINARY BASED (X),
        B FIXED BINARY,
        X POINTER;

X = ADDR (B);
A = 15;

In this example, the variable A is declared as a based variable, with X designated as its pointer. The variable B is an automatic variable; PL/I allocates storage for B when the block is activated. When the ADDR built-in function is referenced, it returns the location in storage of the variable B, and the assignment statement gives this value to the pointer X. This assignment associates the variable A with the storage occupied by B. Because A is based on X and X points to B, an assignment statement that gives a value to A actually modifies the storage occupied by the variable B. Figure 5.4, “Using the ADDR Built-In Function” shows this example.

Figure 5.4. Using the ADDR Built-In Function
Using the ADDR Built-In Function

5.5.6. Data-Type Matching for Based Variables

In most applications, the data type of a based variable reference is identical to the data type under which the accessed storage is allocated. However, it is not required that the data types be identical. The following sections discuss type-matching criteria in more detail.

5.5.6.1. Matching by Overlay Defining

Matching by overlay defining is in effect if the based variable reference and the variable for which the storage was originally allocated are both suitable for character-string or bit-string overlay defining. The only further restriction is that the size n (in characters or bits) of the based variable reference must be less than or equal to the size in characters or bits of the original variable. The based variable reference accesses the first n characters or bits of the storage.

The first program in Section 5.5.7, “Examples of Based Variables” contains an example of this type of matching. The structure members PAY_RECORD.GROSS_PAY (a character string) and HEALTH_RECORD.EXAM DATE (a picture) are not identical data types. However, both are stored as a character string of length 9; therefore, they meet the criteria for string overlay defining and for data-type matching.

5.5.6.2. Matching by Left-to-Right Equivalence

Matching by left-to-right equivalence applies to structured variables that are identical up to a certain point. To see if this applies, examine the declaration of the based variable, and consider only the portion on the left that includes the referenced member and all of the level-2 substructure containing the referenced member (if the member is not itself at level 2). If the original variable's declaration has a similar left part with identical data type, then the based variable reference and the original reference match. For example:

DECLARE 1 S1 BASED (P),
          2 X,
            3 (A,B) FIXED BIN,
          2 Y,
            3 C CHAR(10),
            3 D(5) FLOAT;

DECLARE 1 S2 BASED(P),
          2 X,
            3 (A,B) FIXED BIN,
          2 Y,
            3 C CHAR(10),
            3 E BIT(32);

ALLOCATE S1;

S2.A = 3;   /* valid l-to-r match */
S2.C = 'X'; /* INVALID */

In the first assignment, S2.A is a valid reference because S1 and S2 match through the level-2 structure X. In the second assignment, S2.C is invalid in standard PL/I because the level-2 structures S2.Y and S1.Y do not match. However, the reference to S2.C does work.)

This sort of matching is useful in connection with data structures and files, where the first part of a record contains a value indicating the precise structure of the remainder of the record.

Note that the UNION attribute allows this type of declaration to be written more easily.

5.5.6.3. Nonmatching Based Variable References

In PL/I, a based variable reference need not match the variable for which the storage was originally allocated. The only requirement is that the size of the based variable in bits be less than or equal to the size of the original variable in bits. However, use of such nonmatching references requires knowledge of the internal representation of data. You should not expect the resulting code to be transportable between OpenVMS systems or to other vendors' hardware. For example:

DECLARE X FLOAT BINARY(24);
DECLARE 1 S BASED(ADDR(X)),
          2 FRAC_1 BIT(7),
          2 EXP BIT(8),
          2 SIGN BIT(1),
          2 FRAC_2 BIT(16);

EXP = '0'B;  /* set exponent to 0 */
SIGN = '1'B; /* set sign negative */
X = X + 1;

The declaration of S describes the internal VAX representation of a single-precision floating-point number. The first two assignments set the sign and exponent fields to the reserved operand combination; the assignment to X causes a reserved operand exception.

5.5.7. Examples of Based Variables

The program DEFINED uses based variables and the READ SET statement to process a file of personnel data (PERSONNEL.DAT). The file has two types of valid records, a pay record and a health record, which are identified by a 1-character code in the first position. The two record types are declared as based structures (PAY_RECORD and HEALTH_RECORD), one of which is selected based on the record type character ('P' for pay, 'E' for health). Any record that does not begin with one of these characters is invalid and is written out as a reference to the based character variable INVALID_RECORD.

DEFINED: PROCEDURE OPTIONS(MAIN);

DECLARE P POINTER; /* pointer to structures */

DECLARE 1 PAY_RECORD BASED(P),
          2 RECORD_TYPE CHARACTER(1),
          2 NAME CHARACTER(20),
         /* the two structures differ in this member: */
          2 GROSS_PAY PICTURE '999999V.99';

DECLARE 1 HEALTH_RECORD BASED(P),
          2 RECORD_TYPE CHARACTER(1),
          2 NAME CHARACTER(20),
          2 EXAM_DATE CHARACTER(9);

DECLARE INVALID_RECORD CHARACTER(30) BASED(P);

DECLARE PERSONNEL RECORD FILE;
DECLARE PERSOUT STREAM OUTPUT PRINT FILE;

/* used to control DO group: */
%REPLACE NOTENDFILE BY '1'B;

ON ENDFILE(PERSONNEL) BEGIN;
   PUT FILE(PERSOUT) SKIP LIST
           ('All processing complete.');
   STOP;   /* program stops here */
   END;

OPEN FILE(PERSONNEL) INPUT TITLE('PERSONNEL.DAT');

DO WHILE(NOTENDFILE);
/* terminated by ENDFILE ON-unit */

READ FILE(PERSONNEL) SET(P);
/* P is the location of the
   record acquired by the READ statement */

IF P->PAY_RECORD.RECORD_TYPE = 'P' THEN
   PUT FILE(PERSOUT) SKIP LIST
          ('Name=',P->PAY_RECORD.NAME,
          'Gross pay=',P->GROSS_PAY);

ELSE       /* either a health record or an invalid record */
   DO;
   IF P->HEALTH_RECORD.RECORD_TYPE = 'E' THEN
   PUT FILE(PERSOUT) SKIP LIST
          ('Name=',P->HEALTH_RECORD.NAME,
          'Exam date:',P->EXAM_DATE);
   ELSE /* invalid record type */
   PUT FILE(PERSOUT) SKIP LIST
           ('Invalid record:',P->INVALID_RECORD);
   END;

END; /* repeat DO group until ENDFILE is signaled */

END DEFINED;

For example, assume that the file PERSONNEL.DAT contains these records:

PMary A. Ford        125000.55
EMary A. Ford        22July 80
t12345678901234567890pppppp.pp

The output file (PERSOUT.DAT) will contain the following output:

Name=   Mary A. Ford            Gross pay=      125000.55
Name=   Mary A. Ford            Exam date:      22July 80
Invalid record: t12345678901234567890pppppp.pp
All processing complete.

Notice these other features of the program:

  • The references to based variables have a locator qualifier (P->) for clarity. However, because all are declared with P as their pointer reference, the locator qualifier can be omitted.

  • References to the structure members RECORD_TYPE and NAME must be fully qualified with the name of their containing structures (PAY_RECORD and HEALTH_RECORD) because both structures have members with these names. In contrast, GROSS_PAY and EXAM_DATE are unique to their structures and need not be fully qualified.

The UNION attribute can be used to declare a single record with a variant portion in place of PAY_RECORD and HEALTH_RECORD. For example:

1 RECORD BASED(P),
  2 RECORD_TYPE CHARACTER(1),
  2 NAME CHARACTER(20),
  2 VARIANS UNION,
    3 GROSS_PAY PICTURE '999999V.99',
    3 EXAM_DATE CHARACTER(9)

Note that the UNION attribute is not available in many other PL/I implementations.

5.6. Controlled Variables

A controlled variable is a variable whose actual storage is allocated and freed dynamically in generations, of which only the most recent is accessible to the program. Controlled variables are declared with the CONTROLLED attribute. A controlled variable can be a scalar, array, area, or major structure variable possessing any of the attributes that do not conflict with the CONTROLLED attribute. See Section 2.2.11, “CONTROLLED Attribute” for information about the CONTROLLED attribute.

The CONTROLLED attribute cannot be applied to minor structures, members of structures, parameters, or descriptions in an ENTRY or RETURNS attribute.

A controlled variable has no storage assigned to it until an ALLOCATE statement allocates storage for it. Each storage assignment is a generation of the variable. Subsequent ALLOCATE statements allocate subsequent generations. At any time in the program's execution, a reference to a controlled variable is a reference to the most recent generation of that variable, that is, the generation created by the most recent ALLOCATE statement.

The FREE statement frees the most recent generation of a controlled variable. If an attempt is made to free a controlled variable for which no generation exists (or to refer to such a variable), PL/I signals the ERROR condition. The following example shows the use of controlled variables:

CONT: PROCEDURE OPTIONS (MAIN);

DECLARE STR CHARACTER (10) CONTROLLED;

    ALLOCATE STR;
    STR = 'First';
    ALLOCATE STR;
    STR = 'Second';
    ALLOCATE STR;
    STR = 'Third';
    PUT SKIP LIST (STR);
    FREE STR;
    PUT SKIP LIST (STR);
    FREE STR;
    PUT SKIP LIST (STR);
    FREE STR;

END;

The output of this program is as follows:

Third
Second
First

5.6.1. Using the ALLOCATION Built-In Function

Because only the most recent generation of a controlled variable is available to a program, controlled variables provide an easy way to implement a stack. The ALLOCATE statement is equivalent to a push operation, and the FREE statement is equivalent to a pop operation. The ALLOCATION built-in function returns the number of generations of a variable, so you can use it to find out if the stack is empty. For example:

DECLARE NEXT_MOVE CHARACTER(5) CONTROLLED,
    DIRECTIONS(4) CHARACTER(5) INITIAL(
    'North','East','South','West'),
    D FIXED BINARY (7);
       ⋮
    ALLOCATE NEXT_MOVE;         /* Part of a loop that reports */
    NEXT_MOVE = DIRECTIONS(D);  /* moves in reverse order      */
       ⋮
    DO WHILE                    /* Print moves in correct order */
        (ALLOCATION(NEXT_MOVE) ^= 0);
        PUT SKIP LIST ('Go ', NEXT_MOVE);
        FREE NEXT_MOVE;
    END;

See Section 11.4.7, “ALLOCATION” for more information about the ALLOCATION built-in function.

5.6.2. Using the ADDR Built-In Function

You can use a controlled variable as the argument of the ADDR built-in function. If a generation exists, ADDR returns a pointer to it. If no generation of the variable exists, ADDR returns the null pointer. Thus, you can use ADDR to preserve a pointer to a generation of a controlled variable that later becomes hidden under further generations, as in the following example:

DECLARE STOPS CHARACTER (20) VARYING CONTROLLED,
    MIDPOINT CHARACTER (20) VARYING BASED (P),
    P POINTER;
       ⋮
    ALLOCATE STOPS;
    STOPS = CURRENT_LOC;
    IF I = 5 THEN P = ADDR(STOPS);
       ⋮
    PUT SKIP LIST (
        'End reached!  Halfway point was', MIDPOINT);

At a certain point during the execution of this program, the ADDR built-in function captures the address of the current generation of STOPS and assigns it to P. After more generations of STOPS have been allocated, MIDPOINT (which is based on P) has the value of that same intermediate generation of STOPS.

Note that the value of P (and therefore of MIDPOINT) is valid only so long as the intermediate generation of STOPS to which P points is allocated. As soon as that generation is freed, the value of P becomes invalid, and it must not be used in a pointer-qualified reference until it is reassigned.

A controlled variable cannot be used in a pointer-qualified reference. In the previous example, a reference like the following would be illegal:

P->STOPS

5.7. Dynamically Allocated Variables

This section describes the mechanisms for dynamically allocating storage.

5.7.1. ALLOCATE Statement

The ALLOCATE statement obtains storage for a based or controlled variable and sets (with based variables) a locator variable equal to the address of the allocated storage. The format of the ALLOCATE statement is as follows:

{ ALLOCATE }
{ ALLOC    }
allocate-item,...;

allocate-item

The syntax of the allocate item is:

variable-reference [SET(locator-reference)] [IN(area-reference)]
variable-reference

A based or controlled variable for which storage is to be allocated. The variable can be any scalar value, array, area, or major structure variable; it must be declared with the BASED or CONTROLLED attribute.

SET(locator-reference)

The specification of a pointer or offset variable (for based variables) that is assigned the value of the location of the allocated storage. If the SET option is omitted, the based variable must be declared with BASED(locator-reference); the variable designated by that locator reference is assigned the location of the allocated storage.

You cannot use the SET option to allocate controlled variables.

IN(area-reference)

The specification of an area reference (for based variables) in which the storage is to be allocated. If the IN option is omitted, the SET option (or implied SET option if the locator variable is an offset) must be an offset declared with OFFSET(area-reference).

You cannot use the IN option to allocate controlled variables.

Examples

DECLARE STATE CHARACTER(100) BASED (STATE_POINTER),
        STATE_POINTER POINTER;

ALLOCATE STATE;

This ALLOCATE statement allocates storage for the variable STATE and sets the pointer STATE_POINTER to the location of the allocated storage.

The ALLOCATE statement obtains the amount of storage needed to accommodate the current extent of the specified variable. If, for example, a character-string variable is declared with an expression for its length, the ALLOCATE statement evaluates the current value of the expression to determine the amount of storage to be allocated. For example:

DECLARE BUFFER CHARACTER (BUFLEN) BASED,
         BUF_PTR POINTER;
   .
   .
   .
BUFLEN = 80;
ALLOCATE BUFFER SET (BUF_PTR);

Here, the value of BUFLEN is evaluated when the ALLOCATE statement is executed. The ALLOCATE statement allocates 80 bytes of storage for the variable BUFFER and sets the pointer variable BUF_PTR to its location.

The ALLOCATE statement is also used to allocate storage for controlled variables. A controlled variable is one whose actual storage is allocated and freed dynamically in generations, only the most recent of which is accessible to the program. Unlike based variables, a controlled variable cannot be used in a pointer-qualified reference.

If the variable being allocated has been declared with initial values, these values are assigned to the variable after allocation.

5.7.2. FREE Statement

The FREE statement releases the storage that was allocated for a based or controlled variable. The format of the FREE statement is as follows:

FREE free-item[,free-item ...];
free-item

The syntax of the free-item is:

variable-reference [IN(area-reference)]
variable-reference

A reference to the based or controlled variable whose storage is to be released.

If you do not explicitly free the storage acquired by the variable, the storage is not freed until the program terminates.

If you free a variable that is explicitly associated with a pointer, the pointer variable becomes invalid and must not be used to reference storage. You can only free a variable once for each allocation.

IN(area-reference)

The specification of an area reference (for based variables) in which the storage is to be freed. If the IN option is omitted, the variable reference must be either implicitly or explicitly based on an offset variable with a base area.

You cannot use the IN option in conjunction with controlled variables.

Examples

FREE LIST;
FREE P->INREC;

These statements release the storage acquired for the based variable LIST and for the allocation of INREC pointed to by the pointer P.

ALLOCATE STATE SET (STATE_PTR);
   .
   .
   .
FREE STATE;

This FREE statement releases the storage for the based variable STATE and makes the value of STATE_PTR undefined.

5.7.3. Other Mechanisms for Dynamic Storage Allocation

PL/I has a variety of dynamic storage management mechanisms available besides those for based and controlled variables. You can also use the following mechanisms:

  • Explicitly specified calls to LIB$GET_VM and LIB$FREE_VM

  • Explicitly specified calls to LIB$GET_VM and LIB$FREE_VM using zones

  • Explicitly specified calls to OpenVMS system memory management services

These storage control mechanisms are generally similar in the amount of overhead that they require both in execution time and in storage space, although certain mechanisms have characteristics that make them useful in specific circumstances.

In general, the standard PL/I language manipulation of dynamic memory provides reasonable performance with some built-in checking.

5.8. Defined Variables

The DEFINED attribute indicates that PL/I is not to allocate storage for the variable, but is to map the description of the variable onto the storage of another variable called the base variable. The DEFINED attribute provides a way to access the same data using different names (see Section 2.2.13, “DEFINED Attribute” for a description of the DEFINED attribute).

In a declaration of a defined variable, the DEFINED keyword, which you can abbreviate to DEF, is followed by a variable reference (which must not have the BASED or DEFINED attribute), and optionally by the position in the variable at which the defined variable begins. If you specify the position, you use the POSITION attribute followed by an expression in parentheses. The expression is an integer expression that specifies a position in the base; a value of 1 indicates the first character or bit. You can use the POSITION attribute only when the defined variable satisfies the rules for string overlay defining, which is described later in this section.

When you use the DEFINED attribute in the declaration of a variable, PL/I associates the description of the variable in the declaration with the storage allocated for the variable on which the declaration is defined. For example:

DECLARE NAMES(10) CHARACTER(5) DEFINED (LIST),
        LIST(10) CHARACTER(5);

In this example, the variable NAMES is a defined variable; its data description is mapped to the storage occupied by the variable LIST. Any reference to NAMES or to LIST is resolved to the same location in memory.

With defined variables that meet the criteria for string overlay defining, you can use the POSITION attribute to specify the position in the base variable at which the definition begins. For example:

DECLARE ZIP CHARACTER(20),
        ZONE CHARACTER(10) DEFINED(ZIP) POSITION(4);

This statement declares the variable ZONE and maps it to characters 4 through 13 of the variable ZIP.

The extent of a defined variable is determined at the time of block activation, but the base reference (and the position, if the POSITION attribute is also specified) is interpreted each time the defined variable is referenced. For example:

DECLARE I FIXED,
    A(10) FIXED,
    B FIXED DEFINED(A(I));
DO I = 1 TO 10;
B = I;
END;

The DO group assigns I to A(I) for I = 1,2,...10.

The base reference of a defined variable cannot be a reference to a based variable or to another defined variable. A defined variable and its base reference must satisfy one of the following criteria:

  • They must both be suitable for character-string overlay defining.

  • They must both be suitable for bit-string overlay defining.

5.8.1. String Overlay Defining

If the defined variable is specified with the POSITION attribute, then both the defined variable and the base reference must be suitable for bit- or character-string overlay defining.

In brief, a variable is suitable for overlay defining if it consists entirely of characters or bits, and those characters or bits are packed into adjacent storage without gaps. Such a variable can be treated as a string or interpreted as different types of aggregates. For example:

DECLARE A (10) CHARACTER (5);
DECLARE B (5) CHARACTER (10) DEFINED (A);

A (1) = 'AAAAA';
A (2) = 'BBBBB';
PUT LIST (B(1));

Figure 5.5, “An Overlay Defined Variable” shows a 50-byte region of storage treated either as a 10-element array (A) of 5-character strings or as a 5-element array (B) of 10-character strings.

Figure 5.5. An Overlay Defined Variable
An Overlay Defined Variable

If the defined variable and its base reference have identical data types, a reference to the defined variable is equivalent to the base reference. In the case of overlay defining, the defined variable maps onto part of the base reference's storage as follows:

  1. If the POSITION attribute was specified, let position be its value at the moment of reference; otherwise, let position equal 1.

  2. Let m be the total number of characters (or bits) specified by the data type of the defined variable. (Note that for pictured data, m is the total number of characters in the picture specification, exclusive of the V character.)

  3. A reference to the defined variable accesses m characters (or bits) of the base reference, beginning with the character or bit specified by position. The reference must lie entirely within the base reference; that is, position and m must satisfy the following formula: 1 <= position <= position+m <= n+1

    n is the total number of characters or bits in the base reference.

5.8.2. Rules for Overlay Defining

A variable V is suitable for character-string overlay defining if V is not an unconnected array and if one of the following criteria is satisfied:

  • V has the CHARACTER attribute, but not ALIGNED or VARYING.

  • V has the PICTURE attribute.

  • V is a structure, and each of V's members and submembers that is not a structure satisfies one of the first two criteria.

A variable V is suitable for bit-string overlay defining if V is not an unconnected array and if one of the following criteria is satisfied:

  • V has the BIT attribute but not ALIGNED.

  • V is a structure, and each of V's members or submembers that is not a structure satisfies the first criterion.

5.9. Storage Sharing

Variables that have any of the attributes BASED, DEFINED, UNION, or PARAMETER can share physical storage locations with one or more other variables.

A based variable is not allocated any storage when it is declared. Instead, storage is either located by a locator-qualified reference to the variable or allocated by the ALLOCATE statement. The BASED attribute then allows you to describe the characteristics of a variable, which can then be located by a reference that qualifies the variable's name with any valid pointer value. Based variables are useful when the program must control the allocation of storage for several variables with identical attributes. The creation and processing of a queued or linked list is a common case. For full details on based variables and valid pointer values, see Section 5.5, “Based Variables”.

A defined variable uses the storage of a previously declared variable, which is referenced in the DEFINED attribute. The referenced variable is known as the base of the defined variable. The base can be a character- or bit-string variable, suitable for a technique called string overlay defining. When the base is a string variable, the POSITION attribute can also be specified for the defined variable, giving the position within the base variable's storage at which the overlay defining begins. Defined variables are useful when the program must refer to the same storage by different names. For full details, see Section 5.8, “Defined Variables”

Unions provide capabilities similar to those of defined variables, but the rules governing unions are less restrictive. A union is a variation of a structure in which all immediate members occupy the same storage.

The UNION attribute, which is used only in conjunction with a level number in a structure declaration, signifies that all immediate members of the major or minor designated structure occupy the same storage. Immediate members are those members having a level number one higher than the major or minor structure with the union attribute. For more details, see Section 4.2.2, “Using The UNION Attribute On Structure Declarations”

Parameters of a procedure share storage with their associated arguments. The associated argument is either a variable written in the argument list or a dummy variable allocated by the compiler. When the written argument is a variable, the sharing of storage by the parameter and argument allows a procedure to return values to the invoking procedure by changing the value of the parameter. For instance, a function can return values in this manner in addition to returning the value specified in its RETURN statement. For more information, see Section 7.5, “Parameters and Arguments”.