The CONTROLLED attribute specifies a variable whose storage is dynamically allocated and freed in generations, independent of block boundaries. Controlled variables are allocated only when specified in an ALLOCATE statement. After being allocated, a controlled variable remains allocated until a FREE statement names the variable.
A reference to a controlled variable refers to the generation of the variable created by the most recent ALLOCATE statement. The FREE statement frees the most recent generation of the controlled variable, exposing the previous generation. For example:
DECLARE STACK (STACK_ITEM_SIZE) CHAR(32) CTL; DECLARE STACK_ITEM_SIZE FIXED BIN; DECLARE I FIXED BIN; /* Push a first stack item. */ STACK_ITEM_SIZE = 4; ALLOCATE STACK; DO I = 1 TO STACK_ITEM_SIZE; STACK(I) = CHAR(I) || '_1'; END; /* Push a second stack item. */ STACK_ITEM_SIZE = 3; ALLOCATE STACK; DO I = 1 TO STACK_ITEM_SIZE; STACK(I) = CHAR(I) || '_2'; END; PUT SKIP LIST ('Print second item on the stack'); PUT SKIP LIST (STACK); /* Pop the last element on the stack. */ FREE STACK; PUT SKIP LIST ('Print first item on the stack'); PUT SKIP LIST (STACK); /* Pop the last element on the stack. The stack is empty now. */ FREE STACK;
CONTROLLED variables can be declared with asterisk (*) extents, and when such a variable is allocated, its extents can be made explicit by the use of the ALLOCATE statement with specified attributes. CONTROLLED variables can also be declared with explicit extents (constant or variable), and these explicit extents can be overridden by use of the ALLOCATE statement with specified attributes.
It is also possible to specify attributes with asterisk extents in the ALLOCATE statement. This causes the extents to be copied from the corresponding extents used for the previous allocation of the same CONTROLLED variable. If an asterisk is specified in the ALLOCATE statement for one dimension of a variable, it must be specified for all dimensions of that variable.
If level numbers are specified, the first variable in the ALLOCATE statement must be specified with a level-num of 1 and be declared as a level-1 structure, and in this case, all members of the structure must be included in the ALLOCATE statement just as they appear in the declaration of the structure.
Any bound, length, size, or initial value specified in the ALLOCATE statement overrides the corresponding attribute specified in the declaration of the variable. A bound, length, or size can be specified in the ALLOCATE statement with an asterisk, and, in this case, the bound, length, or size is taken from the current generation of that CONTROLLED variable. If a bound, length, size, or initial value specified in the ALLOCATE statement contains a reference to the variable being allocated, the reference is to the current generation of the variable.
Evaluations of expressions for bounds, lengths, sizes, and initial values during the execution of the ALLOCATE statement are done according to the normal rules for PL/I references, and must be capable of logical execution (that is, without circularity). Evaluations depending on the existence of a prior generation of a variable require that that generation has previously been allocated.
Controlled variables provide an easy way to implement a stack, because only the most recent generation is available to the program. The ALLOCATE statement is equivalent to a push operation and the FREE statement is equivalent to a pop operation. The ALLOCATION built-in function is used to determine the number of generations of a controlled variable that are available.
Examples
DECLARE 1 A CONTROLLED, 2 B CHAR(*), 2 C CHAR(20), 2 D(2) FIXED BIN(31), 2 E(4,4) CHAR(*), 2 F FIXED BIN(31); ... ALLOCATE 1 A, 2 B CHAR(12), 2 C, 2 D(6), 2 E CHAR(3), 2 F INIT(128); ... ALLOCATE 1 A, 2 B CHAR(12), 2 C CHAR(10), 2 D(D(1)), 2 E CHAR(3), 2 F;
In this example, the structure A is first allocated with member B of length 12, with array member D with dimension (6) instead of the declared (2), array member E with its declared dimensions of (4,4) and element lengths of 3, and member F with an initial value of 128. The second generation of A is allocated with member C of length only 10 and with the dimension of array member D the value contained in the first generation of A.D(1).
DECLARE X(*,*) CONTROLLED; ALLOCATE X(3,7); ALLOCATE X(*,*);
In the second allocation of array X, the bounds are implicitly (3,7), as derived from the previous allocation.
For CONTROLLED parameters, CONTROLLED must be explicitly stated in the parameter descriptor for the ENTRY declaration. This is required by the PL/I language specification. For example, if you compile and link together the following two procedures:
t.pl1 TSTENTM: PROC OPTIONS(MAIN); DCL N CHAR(10) CONTROLLED; DCL TSTENT ENTRY(CHAR(10)); ALLOCATE N; N = 'CONTROLLED'; CALL TSTENT(N); END;
t1.pl1 TSTENT: PROC(N); DCL N CHAR(10) CONTROLLED; PUT SKIP LIST ('N = ',N); RETURN; END;
Upon execution you will get:
*** Condition ERROR raised *** Unhandled condition SIGSEGV at PC=004011AE N =
To avoid this, add the controlled attribute to the ENTRY declaration:
DCL TSTENT ENTRY(CHAR(10)CTL);