This chapter discusses the various data types that are available to you in Open PL/I.
Each value has a data type that determines which operations can be performed on the value and the way in which the value is represented in storage.
Data types are declared for variables, function results, named constants (such as files), and external procedures. They are also used with the ENTRY attribute to describe the data type of each parameter of a procedure. (For further information, see the section ENTRY.)
Data types are declared using the following attributes:
Computational Data | Non-Computational Data |
---|---|
FIXED BINARY | POINTER |
FIXED DECIMAL | LABEL |
FLOAT BINARY | ENTRY |
FLOAT DECIMAL | FILE |
PICTURE | OFFSET |
CHARACTER | AREA |
CHARACTER VARYING | |
BIT |
The attributes in the first column represent data items whose values can be manipulated by arithmetic or string operations and are therefore computational. The attributes in the second column refer to data used within a program for control structures and linkage and are therefore non-computational.
The data type of a value produced by an expression is determined by the operands and the built-in functions within the expression. (For the data type produced by each operator, see the section Types of Operators, and for the data type of the values produced by each built-in function, see the chapter Open PL/I Built-Ins.) The data type of a constant is determined by the syntax of the constant.
A variable capable of storing only one value, such as an integer or a character string, at any one time is called a scalar variable. An array variable is a collection of variables all of the same type. For example, an array might consist of 20 individual character strings. The variables contained in the array are called elements of the array. These elements all have the same name as the array. They are ordered by a numeric index, or perhaps several indices, and they are referenced by use of these indices. A structure variable is a collection of variables of arbitrarily mixed types. The variables contained in a structure are called members of the structure. The members each have their own name. A member may be referenced by its name alone, or its name may be qualified by the structure name to distinguish it from a member of another structure. A structure can contain subordinate structures as members, as well as array members and scalar members. Also, the elements of an array can be structures.
This chapter discusses each data type, beginning with computational data types.
Each arithmetic value is characterized by a base (binary or decimal), a scale (fixed-point or floating-point), and a precision (the number of digits in the value). These three properties collectively constitute the data type of the arithmetic value.
The data type of arithmetic values is expressed using the following attributes:
where p is the precision and q is the number of fractional digits in the precision.
Each implementation of PL/I imposes limits on the maximum values of p and q, and these limits typically differ for each combination of base and scale. Refer to your Open PL/I User's Guide for information on the default precision and maximum precision for each arithmetic data type in Open PL/I.
Fixed-point numbers contain p digits. For fixed-point decimal numbers, q of those digits may be fractional digits. Fixed-point binary numbers are always integers (q =0). For example:
DECLARE K FIXED BINARY(15); DECLARE D FIXED DECIMAL(7,2);
In this example, the values of K are fixed-point integers with 15 binary digits. Therefore, K can hold any value in the range -32768 to +32767 or -(2**⊃15) to (2**⊃15)-1. The values of D are fixed-point numbers with seven decimal digits, two of which are fractional digits. Therefore, D can hold any value in the range -99999.99 to +99999.99.
Because most computers operate most efficiently with binary integers, fixed binary variables should be used whenever possible. The relative efficiency of fixed binary variables used as subscripts, string lengths, DO index variables, and so forth is very significant.
Refer to your Open PL/I User's Guide for a description of the -Iongint compiler option, which alters the default precision of fixed binary variables from 15 to 31.
Fixed decimal values generally require more bits of storage and are often more computationally complex than values represented in binary, but fixed decimal values are more efficiently converted to and from an external form (for example, at input and output). In certain instances, fixed decimal values may also be less prone to rounding error than their binary counterparts.
An attempt to calculate a fixed decimal value too large to be supported results in a signal of the ERROR condition. An attempt to calculate a fixed binary value too large to be supported will not result in any error and may cause unexpected results. For rules on precision and scale, see the chapter Data Type Conversions.
Operations involving both decimal and binary values always produce a binary result value. However, if the operation must yield a result that has a fractional part, the result is decimal.
If a fixed-point value is converted to a character string or bit string, the length of the string is determined by the declared precision of the fixed-point value p. For a discussion of the conversion rules, see the chapter Data Type Conversions.
Fixed-point values are never rounded unless explicitly rounded by the use of a built-in function. Assignment to a fixed-point variable truncates excess low-order digits. All possible roundings can be performed by using the CEIL, FLOOR, TRUNC, or ROUND built-in functions, as described in the chapter Open PL/I Built-Ins.
Fixed-point decimal values are always stored as an accurate representation of decimal fractions. A fixed-point decimal value of 10.50 is never represented as 10.49.
Fixed-point constants are written as decimal numbers with or without a decimal point, as shown in the following examples:
5 4.5 4100.01
If a fixed-point constant contains a decimal point, it is considered to be a scaled fixed-point value and is stored and accessed like a fixed decimal variable. Fixed-point constants without a decimal point are integer constants and can be used safely in operations with any other arithmetic value, regardless of that value's base or scale. It is advisable to always write integer constants without a decimal point.
The precision of a fixed-point constant is the number of digits in the constant.
Floating-point data is used to represent values whose magnitudes are too great or too small to be represented by fixed-point data. It may also be used to minimize precision loss in calculations where terms differ considerably in magnitude.
Unexpected results may appear when using floating-point values. The limited precision of floating-point variables and the fact that they cannot precisely represent most fractional values combine to produce behavior in a program that may seem strange. Furthermore, the floating-point implementation on many machines carries more precision in intermediate results than is strictly required by the rules of PL/I. Also, floating-point variables and constants are usually limited to only one or two actual internal precision values. For example, most implementations of the IEEE floating-point standard allow only "short" (23-bit) and "long" (52-bit) floating-point values. Finally, precise results may vary depending on the level of program optimization.
For example, consider the following program:
SAMPLE: PROCEDURE OPTIONS (MAIN); DECLARE (F,G) FLOAT BINARY (23); F = 3111; G = .14; IF F + G ^= 3111.14 THEN PUT LIST ('UNEQUAL RESULT'); END SAMPLE;
This program will actually print the UNEQUAL RESULT message when run on many machines, because the constant .14 and 3111.14 are not precisely representable as floating-point binary numbers. The assignment of .14 to G puts about 23 bits of precision of the constant into the variable. On most implementations, the sum is computed with an actual precision of 52 or more bits, preserving the 23 bits of precision that apply to the fractional part. However, that sum is being compared to the constant 3111.14. The constant will have 23 or 52 bits of precision. (In fact, a strict implementation of the rules of the language requires only that it have 20 bits of precision; for more information, see the chapter Data Type Conversions.) If 3111.14 is represented as a short floating-point number, 14 of the 23 bits of precision are taken up to represent 3111 and hence only 9 bits of the fraction appear in the constant. This will not result in an equal comparison to the constant 3111.14. If the constant is a long floating-point number, 38 of the 25 precision bits will be fractional and the comparison will still fail.
One attempt to fix this might be to change the program as follows:
SAMPLE: PROCEDURE OPTIONS (MAIN); DECLARE (F, G, H) FLOAT BINARY (23); RESULT FLOAT BINARY (23) STATIC INITIAL (3111.14) F = 3111; G = .14; H = F + G IF H ^= 3111.14 THEN PUT LIST ('UNEQUAL RESULT') END SAMPLE;
This appears to truncate the sum to a "real" 23 bits of precision and to compare it to a constant of known precision. This will normally work. However, in the presence of optimization, the value of F + G may be used without reducing its precision through storing it in H and then reloading it.
Thus, as a general rule, comparing floating-point numbers for equality and inequality does not work reliably. Instead, check for equality using the following method:
ABS(FIRST-SECOND) < EPSILON
where the constant EPSILON must be chosen considering the goal of the application.
Floating-point numbers consist of a mantissa m, a base b, and an exponent e. A floating-point number is represented in the following format:
m*b**e
The mantissa m is a fraction containing at least p digits. The value of b and the possible range of e are defined by each implementation (for information on this implementation, refer to your Open PL/I User's Guide); however, if the base is binary, m contains the equivalent of at least p binary digits, and if the base is decimal, m contains the equivalent of at least p decimal digits. For example:
DECLARE X FLOAT BINARY(23); DECLARE Y FLOAT DECIMAL(7);
In this example, the values of X are floating-point numbers whose mantissa contains the equivalent of at least 23 binary digits. The values of Y are floating-point numbers whose mantissa contains the equivalent of at least 7 decimal digits.
The representation of floating-point values in storage also depends on the implementation. An implementation may represent the mantissa in any base that it chooses, provided that it contains an equivalent of at least p binary or p decimal digits. On some computers, decimal floating-point values are represented by using a decimal mantissa, while other implementations use a binary or hexadecimal mantissa for all floating-point numbers.
Open PL/I represents floating-point decimal using a decimal mantissa and decimal exponent, and represents floating-point binary using a binary mantissa and binary exponent. For more information, refer to your Open PL/I User's Guide.
Because floating-point values may be represented in either base, and because excess digits may be lost during calculations, floating-point values may be approximate. However, any integer value that is converted to floating-point and converted back to integer retains its original value. Likewise, the floating-point calculations of addition, subtraction, and multiplication, when performed on integer values, produce integer values.
Floating-point constants are written as fixed-point constants followed by an exponent, as shown in the following examples:
5E+02 4.5E1 100E-04 .001E-04 0E0
Floating-point constants have a decimal precision of p, where p is the number of digits in the fixed-point constant. For example, 4.5E1 has a precision of 2.
When fixed-point constants such as 1.5 are used in operations with floating-point values, they should be written with an exponent to avoid run-time conversion of fixed-point decimal values to floating-point.
If floating-point values are converted to character strings or bit strings, the length of the resulting string is determined by p, not by the actual value. For a discussion of conversion rules, see the chapter Data Type Conversions.
Caution is necessary when using floating-point constants in arithmetic expressions. Floating-point constants are considered to be of type Float Decimal, and the rules for the precision of arithmetic results specify that the precision of the result of a floating-point operation is the maximum of the precisions of the operands. Consider the following example:
SAMPLE2: PROCEDURE OPTIONS(MAIN); DECLARE X FLOAT BIN(52); X = 1E0 + .4999E0; PUT SKIP LIST(X); END SAMPLE2;
The answer printed by this program is, surprisingly, .500000000000000E+000. This is because the two constants are Float Decimal with precisions of 1 and 4, respectively. The addition is performed and the result is converted to Float Decimal(4); the intermediate result of 1.4999 is rounded to 4 decimal digits, and the result is converted to Float Binary and stored in X. An attempt to correct this might be to change the expression to read
X = BINARY(1E0) + BINARY(.4999E0);
This does the computation in Float Binary. However, this converts the first constant to Float Bin(4) and the second to Float Bin(14), and does the computation in short floating-point on most machines. The short floating-point result is lengthened to Float Bin(52) and is then stored in X, giving an answer of 1.499900013208389E+000. The correct fix is to use the two-argument form of the BINARY built-in function:
X = BINARY(1E0,52) + BINARY(.4999E0,52);
This produces long floating-point constants and the computation is done with maximal precision, printing 1.499900000000000E+000. Another solution would be to rewrite the constants using trailing zeros to indicate a more precise constant:
X = 1.0000000000000000E0 + .4999000000000000E0;
This also gives 1.499900000000000E+000, since the computation is done in large enough precision and the result is converted directly to a high-precision floating-point value.
In general, when using floating-point constants in arithmetic expressions with floating-point variables, the precision of the result can be controlled by using the FLOAT and BINARY built-in functions on the operands. The results of operations on two floating-point constants may not be what is expected.
Exponentiation, MOD, and all transcendental functions (including SORT) operations on floating-point decimal data are actually performed using the floating-point binary operations. Thus, the range of the operands and the results of these operations are limited to the ranges for floating-point binary numbers.
Picture data represents fixed-point decimal values that are stored internally as character strings. The strings contain the characters that represent the numeric values in a specified-format (as indicated by special embedded symbols, such as a period or a comma).
Picture data is best used to manipulate a quantity arithmetically and then print or display its value using a special output format.
A pictured value is any value whose data type is either declared by a PICTURE attribute or is acquired by the P format in a FORMAT statement. Picture characters are the special characters that make up the picture specification in the PICTURE attribute and in the P format item. Following are examples of picture specifications declared by the PICTURE attribute:
DECLARE F PICTURE '$ZZ,ZZZV.99CR'; DECLARE G PICTURE '$$$,$$$V.99-'; DECLARE H PICTURE '$**,***V.999'; DECLARE I PICTURE '----9V.999'; DECLARE J PICTURE '9999V9999S';
If the value -1234.56 was assigned to each of the variables in these examples, the resulting pictured values would be as follows:
Repetition factors can be used for sequences of identical picture characters. For example, the following declaration of J is equivalent to the one given above:
DECLARE J PICTURE '(4)9V(4)9S';
Picture characters can affect the numeric interpretation and/or the character representation of the value by marking the position occupied by decimal digits. The picture characters fall into the following categories:
Category | Character | Description |
---|---|---|
decimal place character | V | Affects the numeric interpretation of the value only. The decimal point character is not actually stored. |
digit characters | 9, Z, *, Y | Mark the positions occupied by decimal digits and affect both the numeric interpretation and character representation of the value. |
encoded sign characters | T, I, R | Represent a digit that has the sign of the value encoded in the same position with the digit, thereby affecting both the numeric interpretation and character representation of the value. These characters can be used wherever the digit specifier '9' is valid. |
drifting characters | $, +, –, S | Used to indicate digits, and also symbols to be inserted in the internal representation of a character. (See the section Drifting Characters and Strings.) |
insertion characters | Namely the decimal point (.), comma (,), slash (/), and space (B) | Inserted between digits to indicate that characters are inserted in the internal representation of the pictured value. Drifting characters also function as insertion characters when they are used singly. |
credit and debit | CR, DB | Always specified as two-character symbols. (See the section Credit and Debit Characters.) |
The following list explains the meaning of each picture character.
Character | Meaning |
---|---|
V | Indicates the position of the "assumed" decimal point. All digit positions to the right of the 'V' are fractional digits.
Any value assigned to a pictured value is first scaled so that its decimal point is aligned with the 'V'. Only one 'V' character can appear in a picture. If a picture does not contain the 'V' character, it is assumed to be at the right end of the picture, implying an integer value. |
B, ./ | Indicate the positions at which a space (B), comma, decimal point, or slash, respectively, are inserted. These picture characters are inserted into the pictured value only if they are preceded by a nonzero digit or if they are preceded by a '9' or 'V' picture character. If they are not inserted, a zero suppression character (a blank) or an asterisk (*) is inserted instead.
If an asterisk or drifting string occupies the position preceding the insertion character and that position corresponds to a leading zero, the asterisk or drifting string character also suppresses the insertion character. The 'V' and decimal point (.) must be adjacent to one another to ensure that the decimal point is in the same position in both the numeric and character interpretations. The period should follow the 'V' to ensure that it will be in the correct location and will appear whenever any fractional digit is significant. |
Z | Indicates a decimal digit with leading zero suppression.
The 'Z' causes zero suppression using a blank as the suppression character. A 'Z' must not be preceded by a '9' or a drifting string. A picture containing a 'Z' must also not contain an asterisk (*). If 'Z' picture characters appear to the left of 'V', all leading zeros in positions corresponding to those 'Z' characters are suppressed. Nonzero digits and significant zeros are never suppressed. Suppressed zeros are represented by spaces. If a 'Z' appears to the right of 'V', all digits in the value must be indicated by 'Z' characters. Fractional zeros are then suppressed only if all fractional digits are zero and all integer digits are suppressed. In this case, the internal representation contains only spaces in the digit positions. Note: Refer to the examples in the section Rules for Using Picture Data. |
* | Indicates a decimal digit with leading zero suppression. The '*' causes zero suppression using an asterisk as the suppression character. An '*' must not be preceded by a '9' or a drifting string. A picture containing an '*' must also not contain a 'Z'.
The rules for '*' are otherwise the same as those for 'Z'. |
S | Indicates the position of the plus sign (+) or minus sign (-), which may or may not be drifting. The 'S' causes the plus sign to be inserted if the numeric value is greater than or equal to zero, and causes the minus sign to be inserted if the value is less than zero.
If an 'S' occurs more than once in a picture, the entire field of 'S's is a drifting string and can only contain a 'V' and one or more 'B' , '.' or '/' picture characters. Such a field cannot be preceded by a '9', 'Z', or '*', and if it contains a 'V' followed by one or more 'S's, it cannot be followed by a '9'. The total number of digits represented by a drifting string is one less than the number of 'S's in the field. The digits are zero suppressed and a sign of '+' or '-' is inserted immediately preceding the most significant digit of the value. A single 'S' causes a sign of '+' or '-' to be inserted into the pictured value, at the location implied by the position of 'S' in the picture. |
+ | Indicates the position of the plus sign (+) to be inserted if the numeric value is greater than or equal to zero. Operates exactly like 'S', except that the sign of negative values is indicated by a blank. |
– | Indicates the position of the minus sign (–) to be inserted if the numeric value is less than zero. Operates exactly like 'S', except that the sign of positive values is indicated by a blank. |
$ | Indicates the position of the dollar sign ($) to be inserted. Operates like 'S', except that a '$' is inserted instead of either sign. |
9 | Indicates a decimal digit, including leading zeros. The position occupied by '9' always contains a decimal digit, regardless of whether the digit is, or is not, significant in the numeric interpretation of the pictured value. |
Y | Indicates the position of a decimal digit in which the zero digit is always suppressed by a blank, regardless of the position of 'Y' relative to other picture characters. |
CR | Indicates the position at which 'CR' is inserted if the number is less than zero. These two characters must appear as a pair on the rightmost end of the picture. They are replaced by two blanks in internal representation if the value is not negative. |
DB | Indicates the position at which 'DB' is inserted if the number is less than zero. Like 'CR,' these two characters must appear as a pair, may only appear on the rightmost end of the picture, and are replaced by two blanks in internal representation if the value is not negative. |
T | Indicates the position of a decimal digit with an encoded plus or minus sign. The digit contains an encoded plus sign if the numeric value is greater than or equal to zero and an encoded minus sign if the value is less than zero. (See Table 2-1 for representations of encoded sign digits.) |
I | Indicates the position of a decimal digit with an encoded plus sign if the numeric value is greater than or equal to zero. If the value is less than zero, the position contains an ordinary digit. |
R | Indicates the position of a decimal digit with, possibly, an encoded minus sign if the numeric value is less than zero. If the value is greater than or equal to zero, the position contains an ordinary digit. |
Digit | ASCII Character | Digit | ASCII Character |
---|---|---|---|
–0 | } | +0 | { |
–1 | J | +1 | A |
–2 | K | +2 | B |
–3 | L | +3 | C |
–4 | M | +4 | D |
–5 | N | +5 | E |
–6 | O | +6 | F |
–7 | P | +7 | G |
–8 | Q | +8 | H |
–9 | R | +9 | I |
Used alone in a picture, one drifting character marks the position where a special symbol or space is to be inserted. It does not affect the value's numeric interpretation. In this context, the character must appear either before or after all characters that specify digit positions.
If a series of n (n >1) drifting characters appears in a picture, the rightmost n-1 of the characters in the series also specify digit positions. If the digit is a leading zero, the leading zero is suppressed, and the leftmost character "drifts" to the right. In internal representation, the character appears either in the position of the last drifting character in the series or immediately to the left of the first significant digit, whichever comes first. In this context, the n-1 drifting characters also define a portion of the numeric precision of the picture variable, because they describe at least some positions occupied by decimal digits.
A drifting string is a series of more than one of the same drifting character. Only one drifting string can be used in a picture; other drifting characters can be used only individually to designate insertion characters, not digits.
The 'Z' and '*' characters cannot appear to the right of a drifting string.
A digit position cannot be specified to the left of a drifting string.
The following rules apply to insertion characters embedded in a drifting string:
Note: Refer to the examples in the section Rules for Using Picture Data.
Regardless of which character pair is included, 'CR' or 'DB', that character pair appears in the internal representation if the numeric value is less than zero. If the numeric value is greater than or equal to zero, the associated positions in the internal representation contain two spaces.
The credit and debit characters are always inserted with the same case used in the picture; for example, if lowercase 'db' is used in the picture, then lowercase letters are inserted into the pictured value, and if the combination of uppercase and lowercase characters, such as 'Db', is used, 'Db' is inserted into the pictured value. The 'CR' and 'DB' character pairs cannot be used in the same picture, and neither pair can be used in the same picture with any other character that specifies the sign of the value, namely, 'S', '+', '–' and the encoded-sign characters ('T', 'I', 'R').
The rules for using picture data are as follows:
DECLARE A PICTURE 'ZZZV.ZZ'; DECLARE B PICTURE '***V.**CR';
In this example, a value of . 01 assigned to A and B produces and .
. A value of zero assigned to A and B produces
and ********.
DECLARE A PICTURE 'ZZZV.ZZ' DECLARE B PICTURE '---V.z--'
In this example, A has a precision of (5,2), while B has a precision of (4,2).
Following are more examples of picture format output.
Example | Data | Picture | Output |
---|---|---|---|
1 | 12345.60 | $SSSSSSSSV.99 | $ ![]() |
2 | 1.23 | **********V.99 | .********1.23 |
3 | 1.23 | ZZZZZZZZZZV.99S | ![]() |
4 | 1234.56 | Z.ZZZ ZZZV,99 | ![]() |
5 | 11011111 | 9999B9999 | 1101 ![]() |
6 | 011335 | **/**/** | *1/13/35 |
7 | 1234567890 | 999999999V.99 | 234567890.00 |
8 | .33 | ZV.ZZ | ![]() |
9 | .75 | ZV.ZZ | ![]() |
10 | 1234567.89 | $999,999,999V.99DB | $001,234,567.89 |
11 | -1234567.89 | $999,999,999V.99CR | $001,234,567.89CR |
12 | 1023 | YYYYY | 1 23 |
In example 1, '$' is a static character because it appears only once, whereas 'S' is a drifting character because it appears all the way to the decimal.
In examples 2, 3, and 4, the characters 'Z' and '*' are zero-suppression characters. The asterisk prints wherever a zero or blank would have printed, and the 'Z' substitutes blanks.
In example 5 the insertion character 'B' results in a space (blank). A blank is inserted wherever the 'B' appears in the picture format unless it is in a field of zero-suppression characters that are suppressing zeros at the time.
In example 7, the appearance of the character '9' results in the printing of a digit. All digits 0 through 9 are printed, except the leading one, which is truncated.
In examples 8 and 9, the character 'V', once encountered, turns off suppression; all characters after the 'V' are printed. Note that in Example 2-8, 'V' is not used before the decimal point, so the point is turned off, not on.
Examples 10 and 11 show the result of positive and negative data applied to pictures that contain either 'CR' or 'DB'.
A picture repetition factor is an integer specifying the number of times the following picture character is to be repeated. The repetition factor is enclosed within parentheses (there can be no blanks within the parentheses). If the value of the repetition is 0, the following picture character is ignored. For example:
pic '(4)9V(2)9'
is the same as
pic '9999V99'
A character-string value is a sequence of characters. The number of characters in a sequence is called the length of the sequence. In Open PL/I, the maximum length of a string value is 32767 bytes or characters.
A character-string of zero length is called a null string.
Note: Any string produced as an intermediate result requires storage allocated either on the stack or in the system storage area used to support the ALLOCATE statement. Allocation of a large temporary storage block that exceeds the amount of available storage results in a signal of the ERROR condition.
A character-string variable or character-string valued function is declared with the following attributes:
CHARACTER(n)
or
CHARACTER(n) VARYING
where n is an integer valued expression that specifies the maximum length of all string values that can be held by the variable or returned by the function.
The VARYING attribute causes the string variable or function to hold or return values of varying lengths. Internally, the length of the varying string is recorded along with the value. Varying strings are not padded to assume their maximum length, n. The representation of a varying string variable in storage is such that any string up to n characters may be held by the variable, and the length of the current string is retained as part of the value.
Without the VARYING attribute, a string variable or function always holds or returns values of length n. An assignment to a nonvarying string always extends short values with blanks on the right to make them n characters long.
Assignments of a string of more than n characters to either a VARYING or a nonvarying string variable cause only the leftmost n characters to be assigned and excess characters to be truncated.
Character-string values are compared from left to right using the collating sequence of the computer. Strings of unequal length are compared by effectively extending the shorter string with blanks on the right.
Nonvarying character-string variables always occupy exactly n bytes of storage. As elements of arrays or members of a structure, they begin on the next available byte and are not aligned on word or other storage address boundaries. This permits an array of nonvarying characters to be stored and accessed as if it were a single string. See the section Storage Sharing.
Varying character-string variables always occupy n+2 bytes of storage. The first 2 bytes contain an integer L (0 <= L <= n) that specifies the length of the string value currently stored in the variable. The string text occupies the first L bytes of the storage following the 2 length bytes. The value of the last n–L bytes of the variable is undefined. The alignment of varying character strings is specified in your Open PL/I User's Guide. The value L can be accessed using the LENGTH built-in function. An array of varying character strings cannot be accessed as if it were a single character string.
A character-string constant is written in the following format:
'Any characters except quote'
If an apostrophe is required within the constant, it must be written as an adjacent pair of apostrophes.
'ABC' 'He said, "I don"t know."' ''
In these examples, the second string displays a pair of single quotation marks used within the text string "don't". This text string also shows that the double quotation mark is insignificant as a delimiter of a character-string constant. The string in the last example is a null string.
Repetition factors (as described in the section Picture Repetition Factors) can be used with character and bit strings. For example:
(4)'Abc'
is equivalent to a character constant where 'Abc' is repeated four times, 'AbcAbcAbcAbc'
and
(4) '10'B
is equivalent to
'10101010'B
The maximum string repetition factor is 32767.
A bit-string is a sequence of binary digits (bits). The number of bits in the sequence is called the length of the value.
A bit-string of zero length is called a null string.
A bit-string variable or bit-string valued function is declared with the following attributes:
BIT(n)
or
BIT(n) ALIGNED | UNALIGNED
where n is an integer valued expression that specifies the maximum length of all string values that can be held by the variable or returned by the function.
If a value of less than n bits is assigned to a bit-string variable, the variable is extended on the right with zero bits to make it n bits long. If a value of more than n bits is assigned, only the leftmost n bits are stored, and the excess bits are truncated.
The ALIGNED attribute causes the associated bit-string variable to be aligned on the storage boundary (described in your Open PL/I User's Guide) so as to make it more efficiently accessed. This attribute may also cause the variable to occupy more than n bits, but it does not increase the size of values that can be stored in the variable.
The UNALIGNED attribute causes the associated bit-string variable to be aligned at the next available bit so as to reduce storage requirements. If neither ALIGNED nor UNALIGNED is specified, UNALIGNED is assumed by default.
In the following structure declaration,
DECLARE 1 S ALIGNED, 2 A BIT(3), 2 B BIT(7) UNALIGNED, 2 C BIT(1) UNALIGNED; 2 D BIT(5);
the bit strings are stored as follows:
In this example the A and D strings are aligned because they inherit the ALIGNED attribute from the structure declaration. A bit string occupies a full byte. If the structure S had been declared with the UNALIGNED attribute, the A and D strings would be unaligned.
Bit-string values are compared from left to right, a bit at a time until an inequality is found. The shorter operand is effectively extended by zero bits on the right to make it the length of the other operand for purposes of comparison.
A bit-string is not a word of storage in a computer and is not an arithmetic value. It is a sequence of bits that is always operated upon from left to right, just as a character string is operated upon from left to right.
A bit-string constant is written as a quoted string of zeros and ones followed by a B.
A bit string of length one is a boolean value with the possible values of '0'B and '1'B, which denote false and true, respectively.
Note: No assumptions should be made about the form in which a bit-string is stored.
'1'B '10110'B 'O'B ''B
The last string in the previous set of examples is a null bit string.
Bit-string constants may also be written using a string of characters to represent the bit string:
'character string'Bn
where n is 1, 2, 3, or 4 and is the number of bits each character represents. The table Bit String Values gives the set of permissible characters and shows the corresponding bit-string values. In this table, a '–' indicates 'invalid.'
Character | ||||
---|---|---|---|---|
B or B1 | B2 | B3 | B4 | |
0 | 0 | 00 | 000 | 0000 |
1 | 1 | 01 | 001 | 0001 |
2 | – | 10 | 010 | 0010 |
3 | – | 11 | 011 | 0011 |
4 | – | – | 100 | 0100 |
5 | – | – | 101 | 0101 |
6 | – | – | 110 | 0110 |
7 | – | – | 111 | 0111 |
8 | – | – | – | 1000 |
9 | – | – | – | 1001 |
A | – | – | – | 1010 |
B | – | – | – | 1011 |
C | - | - | _ | 1100 |
D | – | – | – | 1101 |
E | – | – | – | 1110 |
F | – | – | – | 1111 |
For example:
Bit-String | Equivalent Value |
---|---|
'073'B3 | '000111011'B |
'A04'134 | '101000000100'B |
An area is a region of storage in which based variables can be allocated and freed. Area variables can have any storage class and may be scalars or arrays. Explicit declaration with an area attribute has the following format:
AREA[(exp [REFER (variable)])]
or
AREA (*)
The exp used with the area attribute specifies the number of bytes reserved for the area. This size is rounded up to an 8-byte boundary as necessary.
For an area with the STATIC storage class, the size must be an integer constant.
For areas having the AUTOMATIC, CONTROLLED, or BASED storage classes, the size is defined by an integer expression; if an area is a member of a BASED structure, however, the size specification can use the REFER option.
If neither expression nor asterisk is specified, the default size is 1000 bytes. For example,
DECLARE AREA1 AREA(4096), AREA2 AREA, 1 S BASED, 2 X FIXED BIN(15), 2 AREA3(1024 REFER(X));
An alternate way to declare an AREA is implicitly, through its use in the following contexts:
ALLOCATE X IN(A1);
DECLARE 0 OFFSET(A1);
Area variables are always aligned on 8-byte boundaries. The UNALIGNED attribute is not applicable to AREA variables.
Only based variables can be allocated in an area. Use of an area allows related items to be allocated and freed only in that region of storage reserved for the area. The area can be assigned or transmitted as a unit, complete with all the allocations it contains. To preserve the desired alignment constraints in the area, the size of any allocation within the area is always rounded to an 8-byte boundary.
The amount of reserved storage that is actually in use is known as the "extent" of the area. When first allocated (prior to any allocation in it), the extent of the area is zero. The maximum extent is represented by the "area size." When a based variable is allocated or freed, the extent of the area is updated to reflect the change in the amount of storage in use. In addition, when a based variable is freed, the storage it occupied is added to a chain of available storage blocks within the area. The area extent and the free chain are maintained in an 8-byte control block, adjacent to the space reserved for the area.
Areas may be assigned and passed as arguments. However, areas cannot be compared or otherwise have any operators applied to them.
When an area variable is assigned to another, all allocations in the target area are freed and the extent of the target area is set equal to that of the source area.
The AREA condition is raised for an illegal area assignment detected at run-time. This situation occurs if the maximum size of the source area exceeds that of the target area. The AREA condition is also raised when an attempt is made to allocate a based variable within an area that has too little free storage to accommodate it. For more information about the AREA condition, see the "Area" section in the section On in the chapter Statements.
Offset variables are used to indicate the locations of based variables within the area. See the discussion in the section Offset Data.
Locator data is data whose purpose is to specify the location of other, usually based or controlled, data. Open PL/I supports two types of locator data: pointers and offsets.
A pointer value is the address of a variable's storage. For example:
DECLARE K FIXED BINARY(15); DECLARE A(5) FIXED BINARY(15); DECLARE P POINTER; . . . P = ADDR(A(K));
In this example, P is a pointer variable that is capable of holding the address of any variable, with the possible exception of unaligned bit strings (for more information, see your Open PL/I User's Guide). The assignment statement uses the ADDR built-in function to calculate the address of A (K) and assigns that address as the value of P.
A pointer is used with a template to access the storage to which it points. For example:
DECLARE X FIXED BINARY(15) BASED; . . . P->X = 10;
In this example, X is a based variable (that is, a template) describing a fixed-point binary integer. P is the pointer variable from the example at the beginning of this section. The assignment assigns 10 to A (K).
Pointers are often used to locate the storage of dynamically allocated based variables as described in the section Based Variables, but may provide a mechanism for accessing another variable's storage, as shown in the previous two examples.
The null pointer value is produced by the NULL built-in function. It is a unique value that does not address any variable and is used to indicate that a pointer variable does not currently address anything, as shown in the following example:
DECLARE CHAIN_HEAD POINTER; . . . CHAIN_HEAD = NULL();
The internal representation of NULL is determined by the -setnull compiler option. For more information on compiler options, see your Open PL/I User's Guide.
Pointer values may be assigned, compared for equality or inequality, passed as arguments, and returned from functions, but no calculations or conversions can be performed on them. They cannot be transmitted in stream I/O.
If the variable whose storage is addressed by a pointer value is freed, the pointer value should no longer be used. Use of such a pointer to access the storage causes unpredictable results.
The ADDR built-in function must not be used to calculate the address of an unaligned bit data. Consequently, when such a restriction is imposed, pointer values never address unaligned bit-string data. However, all other data can always be addressed by a pointer, regardless of the addressing capabilities of the computer.
Note: The based variable that is used as a template normally must have the same data type as the variable addressed by the pointer. Violations of this rule cause unpredictable results. Programs that violate this rule and produce "correct" results may fail when compiled with optimization enabled or may fail when moved to another implementation of PL/I. For a discussion of storage sharing by based variables, see the section Storage Sharing.
A pointer value cannot be represented by a constant. However, the NULL built-in function can be used in any context, including an INITIAL attribute, where a constant might be desired.
The OFFSET attribute describes a locator data type that is used to specify the location of a based variable within an area. The format of the OFFSET attribute is:
OFFSET [(area-variable)]
An offset variable is a locator used only in conjunction with an area variable. The value of an offset variable specifies the location of a based variable within an area, relative to the beginning of the area. Since an offset value specifies a relative location, the value remains valid even when the area is assigned to another region of storage.
To use an offset variable to identify a particular based variable, the offset variable must be associated with an area. For example:
DECLARE A AREA, O OFFSET(A), X FIXED BASED(O), Z OFFSET, P POINTER; ALLOCATE X IN(A) SET(O); X = 5; /* Implies area and offset */
This association can be done in the declaration by specifying the area variable, as shown in the previous example. This type of declaration has the convenience of implicitly referring to the associated area whenever a reference is made to the offset variable used as a locator.
When an offset value is assigned to or compared with another offset variable, only its actual value is used — the implicit area reference is ignored. However, when used as a locator, or when compared to or assigned to a pointer, the offset value is implicitly converted to a pointer by generating an address derived from the offset value combined with the address of the area with which it is associated. If this association was not done in the declaration, it can still be done using the POINTER built-in function:
P = POINTER(Z, A); /* offset Z was not associated with area A in its declaration */
Note that, if the offset variable did not have an area association in its declaration, then it is only with the POINTER built-in function that the offset variable can be used as a locator to address a variable within an area. For more information, see the description of the POINTER built-in function.
An OFFSET variable may be assigned a value in the following ways:
For example:
DECLARE X FIXED BIN(31 BASED(01), Al AREA, 01 OFFSET(A1); ALLOCATE X; /*sets 01 = to the offset of X in area A1 */
Since the locator for X and the associated area of 01 are both specified in their respective declarations, the previous ALLOCATE statement is equivalent to:
ALLOCATE X IN(A1) SET(01);
A label prefix on a statement, other than a PROCEDURE or FORMAT statement, is a declaration of a statement label.
The LABEL attribute can be used to declare variables and functions whose values are labels. For example:
A: PROCEDURE; DECLARE L LABEL; . . . L1: . . . L = L1;
In this example, the label variable L is assigned a statement label value. A subsequent GOTO L; would transfer control to the statement labeled L1.
A label value is a descriptor that consists of two parts. One part designates a statement (the instructions compiled for the statement) and the other part designates a stack frame of the block that immediately contains the statement. In the previous example, the assignment of L1 to L assigns a designator to the statement labeled L1 as part one of L, and assigns a designator to the current stack frame of procedure A as part two of L. The execution of a subsequent GOTO statement uses the stack frame designator only if the GOTO statement is executed in a block activation other than that indicated by the label value. For example:
A: PROCEDURE; DECLARE L LABEL; . . . L1: . . . L = L1; . . . GOTO L; . . . B: PROCEDURE; . . . GOTO L; . . .
In this example, execution of the first GOTO statement simply transfers control to L1 because it occurs within the same block activation in which L1 was assigned to L (and consequently, the stack frame component of L designates the stack frame that is current when the GOTO statement is executed). However, execution of the second GOTO statement occurs within a block activation other than that whose designator was assigned to L. Execution of the second GOTO statement first uses the stack frame designator of L to terminate the block activation of B and restore the block activation of A. The second GOTO statement then transfers control to the statement labeled L1. If A were a recursive procedure with more than one activation, the statement GOTO L in B would restore the most recent activation of A. Note that a label variable that is given L as a value will denote the activation of A current at the moment L is assigned, which may not be A's most recent activation. Use of a label variable after its associated block has exited may cause unpredictable results.
A single, optionally signed integer constant in the range -32768 to +32767 may be used to subscript a label. An array of labels is declared by the occurrence of subscripted labels. The elements of the array are all the subscripted labels having the same name and appearing within the same block. The highest subscript used is the upper bound of the array. The lowest subscript used is the lower bound of the array. The number of integers between the lower bound and the upper bound, inclusive, is the extent of the array. The array contains one element of each integer in the extent, although there may not be a subscripted label corresponding to the element. If you refer to an undefined element, the result is unpredictable. For example:
GOTO CASE(K); CASE(1): . . . CASE(2): . . . CASE(3): . . . CASE(6): . . .
In this example, CASE is a one-dimensional array of statement labels containing six elements. Elements 4 and 5 are undefined and should not be used. Using an undefined element produces unpredictable results.
Label prefixes on PROCEDURE, FORMAT, BEGIN, and ENTRY statements cannot be subscripted.
Because the label array is declared by label prefixes, it cannot also be declared as a LABEL variable by appearing in a DECLARE statement.
Label values may be assigned, compared for equality or inequality, passed as arguments, and returned from functions, but no calculations or conversions can be performed on them. You may specify potential label values that can be assigned to a LABEL variable. They cannot be transmitted in stream I/O.
Entry constants and variables are used to invoke procedures through specific entry points. Entry values specify entry points and block activations of procedures.
Entry constants are declared by labels or by PROCEDURE or ENTRY statements.
The leftmost label on a PROCEDURE statement is a declaration of the primary entry point of a procedure. Secondary entry points are established by the ENTRY statement. Names of external procedures that are not part of the compiled module must be declared using an ENTRY attribute and, if they are functions, a RETURNS attribute. For example:
DECLARE E ENTRY(FIXED BINARY(15), POINTER); DECLARE F ENTRY((5) FLOAT DECIMAL(7)) RETURNS(FLOAT DECIMAL(7));
In this example, E is declared as a procedure name that requires two arguments and that must be called as a subroutine by a CALL statement. F is declared as a procedure name that must be referenced as a function. F requires an array of five Float Decimal(7) values as its arguments and returns a Float Decimal(7) result.
Procedures, other than external procedures that are not part of the compiled module, are declared by their PROCEDURE statement and cannot be declared in a DECLARE statement.
Failure to declare a referenced external procedure that is part of another program module results in error messages from the Compiler.
Entry variables are variables, including parameters, that take entry values.
The ENTRY attribute together with the VARIABLE attribute can be used to declare variables whose values are entry values. The ENTRY attribute can be used in a RETURNS attribute to indicate that a function returns entry values. For example:
A: PROCEDURE; . . . DECLARE E ENTRY VARIABLE; . . . E = A;
In this example, E is an entry variable that is capable of being assigned any entry value. The assignment causes the value of E to become a descriptor of the procedure named A.
Any parameter attribute or RETURNS attribute specified as part of an ENTRY attribute has no effect on the values that can be assigned to the ENTRY variable. However, when the entry variable is called, the value that it currently holds must designate a PROCEDURE statement whose parameters and RETURNS option match those given in the declaration of the entry variable.
In the previous example, a program that calls E as a function or calls E with arguments is invalid and produces unpredictable results.
Like a label value, an entry value is a descriptor containing two parts. The first part designates an entry point of a procedure, and the second part designates a stack frame of the block in which the procedure is declared. The second part of an ENTRY value that designates an external procedure is null.
The second part of the entry value is relevant only when there is more than one activation of a given procedure, as is the case with recursive procedures; otherwise, an entry value can be considered simply as a designator of an entry point.
Entry values can be assigned, compared for equality or inequality, passed as arguments, and returned from functions, but no calculations or conversions can be performed on them, and they cannot be transmitted in stream I/O.
The following discussion explains the second part of the entry value, called the stack frame designator.
If a procedure references variables local to its containing procedure, the inner procedure must be given a designator to the outer procedure's stack frame for it to be able to access the outer procedure's variables. The designator to the outer procedure's stack frame is the stack frame designator.
Except when the outer procedure has been activated recursively, only one stack frame for the outer procedure exists. The existing stack frame is the one that is always used when the inner procedure references variables declared in the outer procedure.
If the outer procedure has been activated recursively, the entry value used to call the inner procedure determines which stack frame is to be used when the inner procedure references the outer procedure's variables. For example:
A: PROCEDURE RECURSIVE; DECLARE X FIXED BINARY(15); DECLARE E ENTRY VARIABLE STATIC; IF FIRST-TIME THEN E=B; ELSE CALL F; CALL G; . . . B: PROCEDURE; . . . X=5;
In this example, assume that A calls G and that G calls A, then A calls F, and F calls the entry value held by E. When E is called, an activation of B results, because B was assigned to E. When that activation of B references X, two stack frames containing an instance of X exist. Two stack frames exist because two calls of A are still active. However, because the first of these two activations of A assigned B to E, E contains a designator to the first stack frame of A. Consequently, B assigns 5 to the first instance of X. If B were called directly from A, B would always use the most recent stack frame of A when referencing X.
An older stack frame is used only when an entry name is assigned to an entry variable or passed as an argument to an entry parameter. When the entry name is thus passed or assigned, the current stack frame of its containing block is assigned as the second component of the entry value produced by that assignment.
If the procedure name being assigned or passed as an argument is not declared within the block that is assigning or passing it, but is declared in a containing block, the containing block's stack frame is found using the same principle as was used to find X in the previous example. Once found, a designator to that stack frame is assigned as part two of the entry value.
Each stack frame for a block A has a designator to a stack frame of the block that contains A. That designator is the designator that was supplied as the second part of the entry value used to activate A. Unless the activation of A was the result of calling an entry variable or entry parameter, and unless recursion has been used, the stack frame thus designated is also the immediately preceding stack frame.
A file constant is represented by a file control block, which is an internal data structure maintained by Open PL/I. A file control block can be opened or closed and can thereby be connected to various files and devices known to the operating system. For example:
DECLARE F FILE;
In this example, F is a file constant that designates a file control block. That file control block can be opened, closed, and used to perform I/O on files and devices known to the operating system. (For more information on the file control block's use in I/O, see the section File Constants.)
A file variable is represented internally as a longword containing a pointer to a file control block. When evaluated, the value of the file variable is the address of the associated file's control block.
A file variable is capable of being assigned any file value; it is declared using both the FILE and VARIABLE attributes. For example:
DECLARE G FILE VARIABLE; DECLARE F FILE; . . . G = F;
In the previous example, G is a file variable assigned the value of the file constant F. After the assignment, operations on G are equivalent to operations on F because they both designate the file control block belonging to F.
File values may be assigned, compared for equality or inequality, passed as arguments, or returned from functions; however, no calculations or conversions can be performed on them, and they cannot be transmitted by stream l/o.
An array is an ordered set of values all having the same data type. Elements of an array are referenced by their position within the array. Each array has a specified number of dimensions and each dimension has a specified lower and upper bound. The default low bound of a dimension is 1.
Array elements are stored in row-major order. The successive elements of a row are stored in adjacent memory locations. In arrays with more than two dimensions, the net effect is that when the elements are accessed in their storage order, the rightmost subscript varies most rapidly.
DECLARE A(1:4) FIXED BINARY(15); DECLARE B(0:3,0:5) FLOAT BINARY(21); DECLARE C(-2:10) CHARACTER(5); DECLARE D(25,4,2) POINTER;
In these examples, A is a one-dimensional array capable of holding fixed-point binary values. The lower bound is 1, the upper bound is 4, and the number of elements is 4.
B is a two-dimensional array capable of holding floating-point binary values. Both lower bounds are zero, and the upper bounds are 3 and 5. Therefore, B has 4 rows of 6 columns each.
C is a one-dimensional array capable of holding character-string values each of which has 5 characters. Its lower bound is -2 and its upper bound is 10, giving it a total of 13 elements.
D is a three-dimensional array capable of holding pointer values. Because no lower bounds are given, they are assumed to be 1. The array has a total of 200 elements organized as 25 planes, each of which is 4 rows in length and 2 columns in width.
The maximum number of array dimensions is 8. An array bound may be any number in the range -2^31 to 2^31 -1. Array bounds are also subject to the following restrictions:
Notes: The particular implementation of PL/I or the underlying operating system may also impose restrictions on the size of an individual data item, or on the total sizes of static and/or automatic data regions. For more information, see your Open PL/I User's Guide.
The bounds of AUTOMATIC, DEFINED, CONTROLLED, or BASED arrays may be specified by integer-valued expressions, as explained in the chapter Storage Classes. The bounds of STATIC arrays must be integer constants. The bounds of parameter arrays can be either integer constants or an asterisk (*). If an asterisk is given as the bound of a parameter array, it represents both the lower and upper bound and means that the actual bounds of the corresponding array argument are to be used as the bounds of the array parameter. For example:
DECLARE A(0:5) FIXED BINARY; CALL P(A); . . . P: PROCEDURE(X); DECLARE X(*) FIXED BINARY; . . .
During the call of P in the previous example, the bounds of X are (0:5), and any reference to X is a reference to A.
The elements of an array are referenced using as many subscripts as the array has dimensions. For example:
DECLARE A(-2:5,4,3) FIXED BINARY;
In this example, a reference to A (-2 ,1,1) is a reference to the element in the first column of the first row in the first plane. A reference to A (3,2,1) is a reference to the element in the first column of the second row in the sixth plane.
A subscript of an array must be an integer valued expression. The use of fixed-point fractions or floating-point values as subscripts results in an error message from the Compiler. However, such values can be converted to integer values by use of the TRUNC, CEIL, FLOOR, or ROUND built-in functions.
Each subscript must lie within the range specified by its corresponding lower and upper bound. Unless subscript range checking has been requested, the Compiler does not produce code to check the range of subscript values. If checking is requested, any subscript that exceeds its range results in a signal of the ERROR condition.
Subscripted references to array elements can be used in any context that permits a variable reference.
A cross-section of an array may be referenced by using an asterisk as a subscript. The asterisk refers to the entire range of the corresponding dimension (lower bound through upper bound). In the previous example, a reference to A (*,1,1), refers to the first-row, first-column elements in all eight planes. A reference to A ( *, *,*), refers to all 96 elements of the array.
Array cross-sections may be transmitted in stream or record I/O, passed as arguments, and assigned to other arrays of the same size and shape.
A structure is a hierarchically ordered set of values that may be of different data types. The immediate components of a structure are called members of the structure. A structure that is itself a member of another structure is called a substructure. A structure that is not a substructure is called a major structure.
The hierarchical organization of a structure is specified by using level-numbers, as shown in the following example:
DECLARE 1 S, 2 A FIXED BINARY, 2 B FLOAT BINARY, 2 T, 3 P POINTER, 3 Q CHARACTER(10);
In this example, S is a major structure, also called a level-one structure. All major structures must have a level-number of 1. The members of S are A, B, and T. T is a substructure with members P and Q.
Members normally have a level-number that is 1 greater than their containing structure; however, they may be given any level-number that is greater than the level-number of their containing structure and less than the level-numbers they contain. For example:
DECLARE 1 S, 20 A FIXED BINARY, 20 B FLOAT BINARY, 20 T, 30 P POINTER, 30 Q CHARACTER(10);
The structure shown in this example is equivalent to the structure shown in the example immediately preceding it.
An entire structure may be transmitted in stream or record I/O, passed as an argument, or assigned to another structure of identical size and shape and having members of corresponding identical data types, but no conversions or calculations can be performed on entire structures.
If a structure contains one or more members with a variable size, place all such members at the end of the structure following all the members with a fixed size. This placement improves the addressing of the fixed size members.
Members of structures can be referenced in any context that permits a reference to a variable. If a member's name is not otherwise declared in the same scope (that is, in the same block or containing block), the member may be referenced by its name alone.
The name of a member must be unique within its immediately containing structure, but may be used as the name of members of other structures or as the name of a nonmember. If a member's name has been used for more than one object, each reference to the member must be sufficiently qualified by the names of its containing structures to uniquely identify the intended member. In a qualified reference, the names used are listed in their hierarchical order, separated by periods. For example:
DECLARE A FIXED BINARY; DECLARE 1 S, 2 A FLOAT BINARY, 2 T, 3 A POINTER;
In this example, a reference to A is a reference to the fixed-point variable. A reference to S.A is a reference to the floating-point variable. A reference to T.A or S.T.A is a reference to the pointer variable.
Use of the major structure name as a qualifier is recommended. It is also advisable to avoid using the same member name more than once anywhere within the major structure or any contained structures. For more information, see the section Reference Resolution.
A structure may have the UNION attribute applied to it, which causes all immediate members of that structure to occupy the same memory locations. For more information, see the chapter Declarations and Attributes.
Structures, like other variables, can be declared as arrays and can contain arrays as members. For example:
DECLARE 1 S(5), 2 A FIXED BINARY, 2 B(4) FLOAT BINARY, 2 T(3), 3 P POINTER, 3 Q(6) CHARACTER(10);
In this example, S is an array of 5 structures. Each structure contains a fixed- point member followed by an array of 4 floating-point values, followed by an array of 3 substructures. Each substructure contains a pointer variable followed by an array of 6 character-string variables. The entire structure contains 5 occurrences of A, 20 occurrences of B, 15 occurrences of T, 15 occurrences of P, and 90 occurrences of Q.
Each member of a structure array is itself an array because there exist as many instances of the member as there are elements in the structure array. If a member is an array in its own right because it has its own dimension information (as do B, T, and Q in the previous example), the array inherits the additional dimensions from its containing structures. For instance, in the previous example, Q is a three-dimensional array whose bounds are (5,3,6).
Any member of a dimensioned structure is an array and must be referenced using a subscript for each of its dimensions. The subscripts may be written anywhere within the structure-qualified reference, but it is preferable to write each subscript immediately following the name to which it applies. Using the previous example to illustrate this recommended programming practice:
S(K) .A | is a reference to the Kth element of A |
S(K) .B(J) | is equivalent to B (K, J) |
S(K) .T(J) .Q(I) | is equivalent to S.T.Q (K, J, I)
or S.T(K,J) .Q(I) or S(K,J, I) .T.Q,
|
It is not possible to use an asterisk to reference an entire extent (cross section) of a structure array.
Members of dimensioned structures can be used only as arrays in stream I/O and as arguments to array parameters whose bounds have been specified as asterisks. They cannot be assigned, used in record I/O, used in ADDR or DEFINED, or passed to parameters with constant bounds.
Subscripted references to members of dimensioned structures can be used in any context that permits a variable reference.
Refer to your Open PL/I User's Guide for the size and alignment of each Open PL/I data type.
Data not contained in a structure or array is allocated on a word or double word boundary, except for Character(1) and Bit(1) aligned data, which is allocated on a byte boundary.
ALIGNED and UNALIGNED are optional data type attributes that control the storage boundary of data.
ALIGNED allows the Compiler to align the data on an implementation-defined storage boundary, with the possible side effect that more bits of storage are used than specified by the declared length. (Refer to your Open PL/I User's Guide for more information.)
UNALIGNED allows the Compiler to align the data on the next byte boundary (or, in the case of fixed-length bit strings, the next bit) rather than on implementation-defined storage boundaries. This can reduce storage requirements.
ALIGNED or UNALIGNED can be specified for scalar, array, or structure variables. The application of either attribute to a structure is equivalent to applying the attribute to all contained elements that do not have explicit alignment declaration. Operations on ALIGNED data tend to be more efficient; operations on UNALIGNED data usually are slower. They are considered part of the data type for purposes of argument/parameter matching and storage sharing, as described in the sections Parameter Storage Class and Storage Sharing.
The UNALIGNED attribute is the default for character and bit data types, while ALIGNED is the default for all other data types.
The UNALIGNED attribute has no effect with an AREA variable, which is always aligned on an 8-byte boundary.
Refer to your Open PL/I User's Guide for more information on data alignment in general and to the section Bit-String Data for more information on the use of ALIGNED and UNALIGNED with bit-string data.
Copyright © 2009 Micro Focus (IP) Ltd. All rights reserved.