Variables and typed constants
Variable declarations
A variable is an identifier that marks a value that can change. A variable declaration
embodies a list of identifiers that designate new variables and their type.
variable declaration
The type given for the variable(s) can be a type identifier previously declared in a type declaration part in the same block, in an enclosing block, or in a unit; it can also be a new type definition.
When an identifier is specified within the identifier list of a variable declaration, that identifier is a variable identifier for the block in which the declaration occurs. The variable can then be referred to throughout the block, unless the identifier is redeclared in an enclosed block. Redeclaration creates a new variable using the same identifier, without affecting the value of the original variable.
An example of a variable declaration part follows:
var
X, Y, Z: Double;
I, J, K: Integer;
Digit: 0..9;
C: Color;
Done, Error: Boolean;
Operator: (Plus, Minus, Times); Hue1, Hue2: set of Color; Today: Date;
Results: MeasureList;
P1, P2: Person;
Matrix: array[1..10, 1..10] of Double;
Variables declared outside procedures and functions are called global variables and they reside in the data segment. Variables declared within procedures and functions are called local variables and they reside in the stack segment.
The data segment
The maximum size of the data segment is 65,520 bytes. When a program is linked (this happens automatically at the end of the compilation of a program), the global variables of all units used by the program, as well as the program’s own global variables, are placed in the data segment.
If you need more than 65,520 bytes of global data, you should allocate the larger structures as dynamic variables. For more details on this subject, see “Pointers and dynamic variables” on page 33.
The stack segment
The size of the stack segment is set through a $M compiler directive—it can be anywhere from 1,024 to 65,520 bytes. The default stack-segment size is 16,384 bytes for a Windows application.
Windows places special demands on the data and stack segments of your program, so the working maximum stack and the data-segment space can be less than the maximum data and stack segment space mentioned here.
Each time a procedure or function is activated (called), it allocates a set of local variables on the stack. On exit, the local variables are disposed of. At any time during the execution of a program, the total size of the local variables allocated by the active procedures and functions can’t exceed the size of the stack segment.
The $S compiler directive is used to include stack-overflow checks in the code. In the default {$S+} state, code is generated to check for stack overflow at the beginning of each procedure and function. In the {$S-} state, no such checks are performed. A stack overflow can cause a system crash, so don’t turn off stack checks unless you’re absolutely sure that an overflow will never occur.
Absolute variables
Variables can be declared to reside at specific memory addresses, and are then called absolute variables. The declaration of such variables must include an absolute clause following the type:
Note The variable declaration’s identifier list can specify only one identifier when an
absolute clause is present.
The first form of the absolute clause specifies the segment and offset at which the variable is to reside:
CrtMode : Byte absolute $0040:$0049;
The first constant specifies the segment base, and the second specifies the offset within that segment. Both constants must be within the range $0000 to $FFFF (0 to 65,535).
Note Use the first form of the absolute clause very carefully, if at all. While a program is running in protected mode, it might not have access rights to memory areas outside your program. Attempting to access these memory areas will likely crash your program.
The second form of the absolute clause is used to declare a variable “on top” of another variable, meaning it declares a variable that resides at the same memory address as another variable:
var
Str: string[32];
StrLen: Byte absolute Str;
This declaration specifies that the variable StrLen should start at the same address as the variable Str, and because the first byte of a string variable contains the dynamic length of the string, StrLen contains the length of Str.
This second form of the absolute clause is safe to use in Windows programming. Memory you are accessing is within your program’s domain.
Variable references
A variable reference signifies one of the following:
-
A variable
-
A component of a structured- or string-type variable
-
A dynamic variable pointed to by a pointer-type variable This is the
syntax of a variable reference:
variable reference
Note The syntax for a variable reference allows an expression that computes a pointer type value. The expression must be followed by a qualifier that dereferences the pointer value (or indexes the pointer value if the extended syntax is enabled with the {$X+} directive) to produce an actual variable reference.
Qualifiers
A variable reference can contain zero or more qualifiers that modify the meaning of the variable reference.
qualifier
An array identifier with no qualifier, for example, references the entire array:
Results
An array identifier followed by an index denotes a specific component of the array—in this case, a structured variable:
Results[Current + 1]
With a component that is a record or object, the index can be followed by a field designator. Here the variable access signifies a specific field within a specific array component:
Results[Current + 1].Data
The field designator in a pointer field can be followed by the pointer symbol (^) to differentiate between the pointer field and the dynamic variable it points to:
Results[Current + 1].Data^
If the variable being pointed to is an array, indexes can be added to denote components of this array:
Results[Current + 1].Data^[J]
Arrays, strings, and indexes
A specific component of an array variable is denoted by a variable reference that refers to the array variable, followed by an index that specifies the component.
A specific character within a string variable is denoted by a variable reference that refers to the string variable, followed by an index that specifies the character position.
index
The index expressions select components in each corresponding dimension of the array. The number of expressions can’t exceed the number of index types in the array declaration. Also, each expression’s type must be assignment-compatible with the corresponding index type.
When indexing a multidimensional array, multiple indexes or multiple expressions within an index can be used interchangeably. For example,
Matrix[I][J]
is the same as
Matrix[I, J]
You can index a string variable with a single index expression, whose value must be in the range 0..N, where N is the declared size of the string. This accesses one character of the string value, with the type Char given to that character value.
The first character of a string variable (at index 0) contains the dynamic length of the string; that is, Length(S) is the same as Ord(S[0]). If a value is assigned to the length attribute, the compiler doesn’t check whether this value is less than the declared size of the string. It’s possible to index a string beyond its current dynamic length. The characters read are random and assignments beyond the current length don’t affect the actual value of the string variable.
When the extended syntax is enabled (using the {$X+} compiler directive), a value of type PChar can be indexed with a single index expression of type Word. The index expression specifies an offset to add to the character pointer before it’s dereferenced to produce a Char type variable reference.
Records and field designators
A specific field of a record variable is denoted by a variable reference that refers to the record variable, followed by a field designator specifying the field.
field designator
These are examples of a field designator:
Today.Year Results[1].Count Results[1].When.Month
In a statement within a with statement, a field designator doesn’t have to be preceded by a variable reference to its containing record.
Object component designators
The format of an object component designator is the same as that of a record field designator; that is, it consists of an instance (a variable reference), followed by a period and a component identifier. A component designator that designates a method is called a method designator. A with statement can be applied to an instance of a class type. In that case, the instance and the period can be omitted in referencing components of the class type.
The instance and the period can also be omitted within any method block, and when they are, the effect is the same as if Self and a period were written before the component reference.
Pointers and dynamic variables
The value of a pointer variable is either nil or the address of a dynamic variable.
The dynamic variable pointed to by a pointer variable is referenced by writing the pointer symbol (^) after the pointer variable.
You create dynamic variables and their pointer values with the procedures New and GetMem. You can use the @ (address-of) operator and the function Ptr to create pointer values that are treated as pointers to dynamic variables.
nil doesn’t point to any variable. The results are undefined if you access a dynamic variable when the pointer’s value is nil or undefined.
These are examples of references to dynamic variables:
P1^ P1^.Sibling^ Results[1].Data^
Variable typecasts
Variable typecasting changes the variable reference of one type into a variable reference of another type. The programmer is responsible for determining the validity of a typecast.
variable typecast
When a variable typecast is applied to a variable reference, the variable reference is treated as an instance of the type specified by the type identifier. The size of the variable must be the same as the size of the type denoted by the type identifier.
A variable typecast can be followed by one or more qualifiers, as allowed by the specific type.
Some examples of variable typecasts follow:
type
TByteRec = record
Lo, Hi: Byte;
end;
TWordRec = record
Low, High: Word;
end;
TPtrRec = record
Ofs, Seg: Word;
end;
PByte = ^Byte;
var
B: Byte;
W: Word;
L: Longint;
P: Pointer;
begin
W := $1234;
B := TByteRec(W).Lo;
TByteRec(W).Hi := 0;
L := $01234567;
W := TWordRec(L).Low;
B := TByteRec(TWordRec(L).Low).Hi; B := PByte(L)^;
P := Ptr($40,$49);
W := TPtrRec(P).Seg;
Inc(TPtrRec(P).Ofs, 4);
end.
Notice the use of the TByteRec type to access the low- and high-order bytes of a word. This corresponds to the built-in functions Lo and Hi, except that a variable typecast can also be used on the left side of an assignment. Also, observe the use of the TWordRec and TPtrRec types to access the low- and high-order words of a long integer and the offset and segment parts of a pointer.
Object Pascal fully supports variable typecasts involving procedural types. For example, given the declarations
type
Func = function(X: Integer): Integer;
var
F: Func;
P: Pointer;
N: Integer;
you can construct the following assignments:
F := Func(P); { Assign procedural value in P to F } Func(P) := F; { Assign procedural value in F to P } @F := P; { Assign pointer value in P to F }
P := @F; { Assign pointer value in F to P } N := F(N); { Call function via F }
N := Func(P)(N); { Call function via P }
In particular, notice that the address operator (@), when applied to a procedural variable, can be used on the left side of an assignment. Also, notice the typecast on the last line to call a function via a pointer variable.
Typed constants
Typed constants can be compared to initialized variables—variables whose values are defined on entry to their block. Unlike an untyped constant, the declaration of a typed constant specifies both the type and the value of the constant.
typed constant declaration
typed constant
Typed constants can be used exactly like variables of the same type and they can appear on the left-hand side in an assignment statement. Note that typed constants are initialized only once—at the beginning of a program. Therefore, for each entry to a procedure or function, the locally declared typed constants aren’t reinitialized.
In addition to a normal constant expression, the value of a typed constant can be specified using a constant-address expression. A constant-address expression is an expression that involves taking the address, offset, or segment of a global variable, a typed constant, a procedure, or a function. Constant-address expressions can’t reference local variables (stack-based) or dynamic (heap-based) variables, because their addresses can’t be computed at compile time.
Simple-type constants
Declaring a typed constant as a simple type specifies the value of the constant:
const
Maximum: Integer = 9999; Factor: Real = -0.1; Breakchar: Char = #3;
As mentioned earlier, the value of a typed constant can be specified using a constant-address expression, that is, an expression that takes the address, offset, or segment of a global variable, a typed constant, a procedure, or a function. For example,
var
Buffer: array[0..1023] of Byte;
const
BufferOfs: Word = Ofs(Buffer); BufferSeg: Word = Seg(Buffer);
Because a typed constant is actually a variable with a constant value, it can’t be interchanged with ordinary constants. For example, it can’t be used in the declaration of other constants or types:
const
Min: Integer = 0; Max: Integer = 99;
type
Vector = array[Min..Max] of Integer;
The Vector declaration is invalid because Min and Max are typed constants.
String-type constants
The declaration of a typed constant of a string type specifies the maximum length of the string and its initial value:
const
Heading: string[7] = 'Section'; NewLine: string[2] = #13#10; TrueStr: string[5] = 'Yes'; FalseStr: string[5] = 'No';
Structured-type constants
The declaration of a structured-type constant specifies the value of each of the structure’s components. Object Pascal supports the declaration of array, record, and set type constants. File type constants and constants of array, and record types that contain file type components aren’t allowed.
Array-type constants
The declaration of an array-type constant, enclosed in parentheses and separated by commas, specifies the values of the components.
array constant
This is an example of an array-type constant:
type
TStatus = (Active, Passive, Waiting); TStatusMap = array[TStatus] of string[7];
const
StatStr: TStatusMap = ('Active', 'Passive', 'Waiting');
This example defines the array constant StatStr, which can be used to convert values of type TStatus into their corresponding string representations. These are the components of StatStr:
StatStr[Active] = 'Active' StatStr[Passive] = 'Passive' StatStr[Waiting] = 'Waiting'
The component type of an array constant can be any type except a file type. Packed string-type constants (character arrays) can be specified both as single characters and as strings. The definition
const
Digits: array[0..9] of Char = ('0', '1', '2', '3', '4', '5',
'6', '7', '8', '9');
can be expressed more conveniently as
const
Digits: array[0..9] of Char = '0123456789';
When the extended syntax is enabled (using a {$X+} compiler directive), a zero- based character array can be initialized with a string that is shorter than the declared length of the array. For example,
const
FileName = array[0..79] of Char = 'TEST.PAS';
In such cases, the remaining characters are set to NULL (#0) and the array effectively contains a null-terminated string. For more about null-terminated strings, see Chapter15.
Multidimensional-array constants are defined by enclosing the constants of each dimension in separate sets of parentheses, separated by commas. The innermost constants correspond to the rightmost dimensions. The declaration
type
TCube = array[0..1, 0..1, 0..1] of Integer;
const
Maze: TCube = (((0, 1), (2, 3)), ((4, 5), (6, 7)));
provides an initialized array Maze with the following values:
Maze[0, 0, 0] = 0
Maze[0, 0, 1] = 1
Maze[0, 1, 0] = 2
Maze[0, 1, 1] = 3
Maze[1, 0, 0] = 4
Maze[1, 0, 1] = 5
Maze[1, 1, 0] = 6
Maze[1, 1, 1] = 7
Record-type constants
The declaration of a record-type constant specifies the identifier and value of each field, enclosed in parentheses and separated by semicolons.
record constant
( : )
;
Some examples of record constants follow:
type
TPoint = record
X, Y: Real;
end;
TVector = array[0..1] of Point;
TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
TDate = record
D: 1..31;
M: Month;
Y: 1900..1999;
end;
const
Origin: TPoint = (X: 0.0; Y: 0.0);
Line: TVector = ((X: -3.1; Y: 1.5), (X: 5.8; Y: 3.0));
SomeDay: TDate = (D: 2; M: Dec; Y: 1960);
The fields must be specified in the same order as they appear in the definition of the record type. If a record contains fields of file types, the constants of that record type can’t be declared. If a record contains a variant, only fields of the selected variant can be specified. If the variant contains a tag field, then its value must be specified.
Set-type constants
Just like a simple-type constant, the declaration of a set-type constant specifies the value of the set using a constant expression. Here are some examples:
type
TDigits = set of 0..9; TLetters = set of 'A'..'Z';
const
EvenDigits: TDigits = [0, 2, 4, 6, 8];
Vowels: TLetters = ['A', 'E', 'I', 'O', 'U', 'Y']; HexDigits: set of '0'..'z' = ['0'..'9', 'A'..'F', 'a'...f'];
Pointer-type constants
The declaration of a pointer-type constant uses a constant-address expression to specify the pointer value. Some examples follow:
type
TDirection = (Left, Right, Up, Down); TStringPtr = ^String;
TNodePtr = ^Node;
TNode = record
Next: TNodePtr;
Symbol: TStringPtr;
Value: TDirection;
end;
const
S1: string[4] = 'DOWN'; S2: string[2] = 'UP'; S3: string[5] = 'RIGHT'; S4: string[4] = 'LEFT';
N1: TNode = (Next: nil; Symbol: @S1; Value: Down); N2: TNode = (Next: @N1; Symbol: @S2; Value: Up); N3: TNode = (Next: @N2; Symbol: @S3; Value: Right); N4: TNode = (Next: @N3; Symbol: @S4; Value: Left); DirectionTable: TNodePtr = @N4;
When the extended syntax is enabled (using a {$X+} compiler directive), a typed constant of type PChar can be initialized with a string constant. For example,
const
Message: PChar = 'Program terminated'; Prompt: PChar = 'Enter values: '; Digits: array[0..9] of PChar = (
'Zero', 'One', 'Two', 'Three', 'Four',
'Five', 'Six', 'Seven', 'Eight', 'Nine');
The result is that the pointer now points to an area of memory that contains a zero- terminated copy of the string literal. See Chapter15, “Using null-terminated strings,” for more information.
Procedural-type constants
A procedural-type constant must specify the identifier of a procedure or function that is assignment-compatible with the type of the constant, or it must specify the value nil.
procedural constant
Here’s an example:
type
TErrorProc = procedure(ErrorCode: Integer);
procedure DefaultError(ErrorCode: Integer); far; begin
Writeln('Error ', ErrorCode, '.');
end;
const
ErrorHandler: TErrorProc = DefaultError;
C h a p t e r