In this discussion, the term most significant bit means the leftmost bit in an external representation of a string, as, for example, when the string is output by the PUT LIST statement. The least significant bit is the rightmost bit in the external representation.
The notion of significance has no meaning for bit strings unless they are used to store integers. PL/I permits the use of bit strings for this purpose, and has defined rules for conversions between bit strings and other data types.
The use of PL/I bit-string data to store integers is not recommended, for two reasons:
You should use the UNSPEC built-in function and UNSPEC pseudovariable when you must store integers in a compact form. Otherwise, use the data types FIXED BINARY and FIXED DECIMAL for integer arithmetic.
Sophisticated applications that depend on the internal representation of bit strings and other types of data may not be directly transportable from other PL/I implementations to Kednos PL/I.
In Kednos PL/I, bit strings are stored in memory with the leftmost bit (as represented by PUT LIST) in the lowest memory location, and bits following the leftmost in successively higher memory locations. This representation of a bit string is reversed by PUT with respect to a conventional picture of memory locations, in which the lowest location appears on the right and higher locations on the left. If you are accustomed to using PL/I on computers other than OpenVMS systems and if you do not change your data to correct for this difference, the result is likely to be in error.
If you wish to use bit strings to represent integers and you would like to associate the leftmost bit (as it might be represented in a PUT LIST statement) with the Most significant bit, use the BINARY builtin function, an example of which appears in Section 11.4.43.
Unaligned Bit StringsAn unaligned bit string is stored beginning at an arbitrary bit location in storage; this location is the location of the most significant bit. The subsequent, less significant, bits are stored in progressively higher locations in memory, as shown in Figure 3-8.
Figure 3-8 Unaligned Bit String Storage
The following programming sequence shows how a value for an unaligned bit-string variable is stored:
DECLARE ABIT BIT (10); ABIT = '1011'B;
After the assignment, the variable appears in storage as shown in Figure 3-9.
Figure 3-9 Sample Unaligned Bit String Storage
Here, n is the length specified for the bit string.
Beginning at bit 0 (the lowest memory location) of the lowest allocated byte, the bit string is stored like unaligned bit-string data; that is, the beginning bit is used to hold the most significant bit in the string. Less significant bits are stored in progressively higher memory locations. Unused bits are set to 0 each time the bit-string variable is assigned a value.
The representation is shown in Figure 3-10.
Figure 3-10 Aligned Bit String Storage
The following programming sequence shows how values are stored for aligned bit strings:
DECLARE ABIT BIT (10) ALIGNED; ABIT = '10011'B;
In this example, the variable ABIT is aligned. When it is assigned the value 10011, the value is stored as shown in Figure 3-11.
Figure 3-11 Sample Aligned Bit String Storage
PL/I defines conversions between bit-string data and other data types, and the PL/I compiler carries out these conversions. However, the conversions defined by PL/I are not always straightforward or intuitive; the padding and truncation that take place during assignment of bit strings of different lengths result in implicit multiplication or division of the bit string's integer value. For example:
DECLARE BITSTR BIT (10); BITSTR = 1; PUT LIST (BITSTR);
The output is:
The result may seem incorrect, but it conforms to PL/I's rules for conversion to bit strings. In this case, the fixed-decimal constant 1 is converted to a FIXED BINARY(4) value, which is in turn converted to an intermediate bit string of length 4:
Next, this intermediate bit string is assigned to the variable BITSTR. Because BITSTR is of length 10, the intermediate bit string is padded on the right with zeros, producing the result as output by PUT LIST. If you now attempt to interpret the value of BITSTR as an integer (for example, by using BITSTR as the argument of the BINARY built-in function), the result would be 64, not 1.
Extra execution time is required to reverse the order of bits when the integer's value is computed. Using arithmetic variables to represent integers is more efficient.
Because of the unexpected results and longer execution time, avoid
using bit strings to represent integers or other data types.
3.5 Pointer Data
A pointer is a variable whose value represents the address in memory of another variable or data item.
Pointers are used to qualify references to based variables, that is, variables for which storage is explicitly allocated at run time by the ALLOCATE statement. For example:
DECLARE LIST_POINTER POINTER; DECLARE 1 LIST_STRUCTURE BASED, 2 FORWARD_PTR POINTER, 2 MEMBER_NAME CHAR(20) VAR; ALLOCATE LIST_STRUCTURE SET (LIST_POINTER); LIST_POINTER -> LIST_STRUCTURE.MEMBER_NAME = 'newname';
When these statements are executed, the ALLOCATE statement allocates storage for a variable LIST_STRUCTURE and sets the pointer LIST_POINTER to the address in memory of the allocated storage. This dynamically created variable is called an allocation of the variable LIST_STRUCTURE.
In the assignment statement, the locator qualifier (->) and the identifier LIST_POINTER distinguish this allocation of LIST_STRUCTURE from allocations created by other ALLOCATE statements, if any. Pointers may also be used directly in declarations of based variables. For example:
DECLARE X POINTER, BUFFER CHARACTER(80) BASED (X);
The variable X is given the POINTER attribute. Then it is used as the target pointer in another declaration, which defines a buffer to be based on X.
This section discusses the following:
Expressions containing pointer variables are restricted to the relational operators equal (=) and not equal (^=).
For example, to test whether a pointer is currently pointing to valid storage, you can write the following statement:
IF LIST_POINTER ^= NULL() THEN DO;
The NULL built-in function always returns a null pointer value.
You can use pointer variables in simple assignment statements that assign a pointer value to a pointer variable. For example:
LIST_POINTER_1 = LIST_POINTER_2; LIST_END = NULL();
You can also use a pointer variable as the source or target in an
assignment statement involving an offset variable or offset value.
3.5.2 Internal Representation of Pointer Data
A pointer occupies a longword (32 bits) of storage and represents a
virtual memory address.
3.6 Offset Data
You declare an offset variable with the OFFSET attribute, optionally followed by an area variable reference. The value of the offset variable will be interpreted as an offset within the specified area, unless the POINTER function is used to explicitly specify another area. You must omit the area reference if the OFFSET attribute is specified within a returns descriptor, parameter declaration, or parameter descriptor. For example:
DECLARE MAP_SPACE AREA (40960), MAP_START OFFSET (MAP_SPACE), MAP_LIST(100) CHARACTER(80) BASED (MAP_START);
These declarations define an area named MAP_SPACE; an offset variable, MAP_START, that will contain offset values within that area; and a based variable whose storage is located by the value of MAP_START.
Offset variables are given values by assignment from existing offset values or from conversion of pointer values. The OFFSET built-in function (described in Section 11.4.59) converts a pointer value to an offset value. PL/I also automatically converts a pointer value to an offset value, or an offset value to a pointer value, in an assignment statement. The following assignments are valid:
In assignment 2, any area references are ignored in the assignment; therefore, the offset value and variable can refer to different areas. In assignments 3 and 4, the offset variable must have been declared with an area reference.
Expressions containing offset variables are restricted to the
relational operators = and ^=, for testing the equality or inequality
of two values.
3.7 Label Data
A label identifies a statement so that it can be referred to elsewhere in the program, for example, as the target of a GOTO statement. A label precedes a statement and consists of any valid identifier terminated by a colon. Some examples are:
TARGET: A = A + B; READ_LOOP: READ FILE (TEXT) INTO (TEMP);
These statements contain the implicit declarations of the names TARGET and READ_LOOP as label constants.
No statement can have more than one label. A statement can, however, be preceded by any number of labeled null statements. For example:
A: ; B: DO I = 1 TO 5;
Other statements in the program can refer to the DO statement in this example by specifying either label A or label B.
A name occurring as a statement label is implicitly declared as a label constant. It has the attributes LABEL and constant. You cannot explicitly declare label constants.
This section discusses the following:
Any label constant except the label of a PROCEDURE or FORMAT statement can have a single subscript. Subscripts must be specified with integer constants; a subscript must appear in parentheses following the label name. An example for VAX and Alpha is:
PART(1): . . . PART(2): . . . PART(*):
When labels are written this way, the unscripted label name represents the implicit declaration of a label array constant. In this example, the array is named PART and is treated as if it were declared within the block containing the subscripted labels. In VAX, a default label can be created by using the asterisk (*) in place of a label constant. If a default label is used, it must be the last label in the list. If the variable subscript is out of range and the default label is present, the default label will be executed.
Elements of the array can be referenced in GOTO statements that specify a subscript. For example:
Within a single block, you cannot use the same subscript value in two different subscripted references with the same name. For example:
If a name is used as a label array constant in two or more different blocks, each declaration of the name is treated as an internal declaration. For example:
LIST(2): RETURN; BEGIN; GOTO LIST (ELEMENT); LIST(1):; LIST(3): END;
In this example, the value of ELEMENT cannot cause control to pass to
the RETURN statement labeled LIST(2) in the containing block. The
subscripted LIST labels in the begin block restrict the scope of the
name to that block.
3.7.2 Label Values
Whenever a reference to a label constant is interpreted, the result is a label value. A label value has two components:
The GOTO statement with a label reference transfers control to the designated statement in the designated block activation. If the target block activation is different from the block activation in which the GOTO statement is executed, then the GOTO is nonlocal. For example:
DECLARE LV LABEL; /* LABEL variable */ . . . LV = L; /* assigns a bound label value to LV */ BEGIN; . . . GOTO LV; /* nonlocal GOTO */ END; L: RETURN;
Operations on label values are restricted to the operators = and ^= for testing the equality or inequality of two values. Two values are equal if they refer to the same statement in the same block activation.
Any reference to a label value after its block activation ceases to
exist is an error with unpredictable results.
3.7.3 Label Variables
When an identifier is explicitly declared with the LABEL attribute, it acquires the VARIABLE attribute by default. You can use such a variable to denote different label values during the execution of the program. For example:
DECLARE PROCESS LABEL; . . . IF CODE THEN PROCESS = BILLING; ELSE PROCESS = CHARGE; . . . GOTO PROCESS;
When the GOTO statement evaluates the reference to the label PROCESS, the result is the current value of the variable. The GOTO statement transfers control to either of the labels BILLING or CHARGE, depending on the current value of the Boolean variable CODE.
You can also give values to label variables by passing label values as arguments or by returning a label value as the value of a function (although the latter method can lead to programming errors that are difficult to diagnose). For example:
CALL COMPUTER(ERROR_EXIT, YVAL, XVAL); . . . ERROR_EXIT:
In this example, the actual argument that is passed for ERROR_EXIT is a dummy argument whose value consists of the following:
Any statement in a PL/I program can be labeled except the following:
Labels on PROCEDURE, ENTRY, and FORMAT statements are not considered statement labels and cannot be used as the targets of GOTO statements.
An identifier occurring as a label in a block cannot be declared in
that block (except as a structure member), and cannot occur in a
parameter list of that block.
3.7.4 Internal Representation of Variable Label Data
Figure 3-12 shows the internal representation of variable label data.
Figure 3-12 Variable Label Data Representation
Entry constants and variables are used to invoke procedures through specified entry points. An entry value specifies an entry point and a block activation of a procedure.
This section discusses the following:
You declare entry constants by using labels on PROCEDURE or ENTRY statements.
You declare internal entry constants by using labels on PROCEDURE or ENTRY statements whose procedure blocks are nested in another block. You can use an internal entry constant anywhere within its scope to invoke its procedure block.
You declare external entry constants either by using labels on PROCEDURE or ENTRY statements that belong to external procedures, or by explicitly declaring the constant names with the ENTRY attribute. You can use an external entry constant to invoke its procedure block from any program location that is within its scope. Its scope is either the scope of its declaration (as a label) or the scope of a DECLARE statement for the constant.
In DECLARE statements, you declare external entry constants with the
ENTRY attribute. The declaration must agree with the actual entry
point. That is, the declaration of the external entry constant must
contain parameter descriptors for any parameters specified at the entry
point, and, if the entry constant is to be used in a function
reference, the declaration must have a returns descriptor describing
the returned value.
3.8.2 Entry Values
Whenever a reference to an entry constant is interpreted, the result is an entry value. An entry value is the entry point of a procedure, and it serves to activate the block in which the entry point is declared (that is, the block in which the entry point's name appears as the label of a PROCEDURE or ENTRY statement). This block activation is the current block activation if the entry point belongs to the current block. If the entry point belongs to a containing block, the activation is on the chain of parent activations that ends at the current block activation.
No conversions are defined between entry data and other data types. You can assign an entry variable only the value of an entry constant or the value of another entry variable. The only operations that are valid for entry data are comparisons for equality (=) and inequality (^=). Two entry values are equal if they refer to the same entry point in the same block activation.
PL/I supports the passing of external procedures, but not
internal procedures, as entry value parameters. To pass an internal
procedure, use an entry parameter.
3.8.3 Entry Variables
Entry variables are variables (including parameters) that take entry values. If the VARIABLE attribute is specified with the ENTRY attribute in a DECLARE statement, the declared identifier is an entry variable. You can assign to an entry variable either another entry variable or an entry constant.
When an entry variable is used to invoke a procedure, its declaration must agree with the definition of the entry point. If the value you assign to an entry variable specifies an entry point with parameters, the parameters must be described with parameter descriptors in the declaration of the variable. If the assigned value specifies an entry point that is invoked as a function, then the declaration of the entry variable must have a RETURNS attribute that describes the data type of the returned value.
The scope of an entry variable name can be either internal or external. If neither the EXTERNAL nor the INTERNAL attribute is specified with the entry variable, the default is INTERNAL.
You can use the entry variable to represent different entry points during the execution of the PL/I program. For example:
DECLARE E ENTRY VARIABLE, (A,B) ENTRY; E = A; CALL E;
The entry constant A is assigned to the entry variable E. The CALL statement results in the invocation of the external entry point A.
You can also declare arrays of entry variables. The following example shows an array of external functions:
DECLARE EXTRACT(10) ENTRY (FIXED,FIXED) VARIABLE RETURNS (FLOAT), GETVAL FLOAT; GETVAL = EXTRACT(3)(1,3);
This assignment statement references the third element of the array EXTRACT. When the statement is executed, this array element must contain a valid entry value.
Exercise caution using static entry variables. The value of a static entry variable is valid only as long as the block in which that value was declared is active.