There are 7 major type classes :
Type declaration
The last class, type identifier, is just a means to give another name to a type. This gives you a way to make types platform independent, by only using your own types, and then defining these types for each platform individually. The programmer that uses your units doesn't have to worry about type size and so on. It also allows you to use shortcut names for fully qualified type names. You can e.g. define system.longint as Olongint and then redefine longint.
Types
Simple types
Type | Range | Size in bytes |
Byte | 0 .. 255 | 1 |
Shortint | -128 .. 127 | 1 |
Integer | -32768 .. 32767 | 23.1 |
Word | 0 .. 65535 | 2 |
Longint | -2147483648 .. 2147483647 | 4 |
Cardinal | 0..4294967295 | 4 |
Int64 | -9223372036854775808 .. 9223372036854775807 | 8 |
QWord | 0 .. 18446744073709551615 | 8 |
B := True; B := False; B := 1<>2; { Results in B := True }Boolean expressions are also used in conditions.
Remark: In Free Pascal, boolean expressions are always evaluated in such a way that when the result is known, the rest of the expression will no longer be evaluated (Called short-cut evaluation). In the following example, the function Func will never be called, which may have strange side-effects.
... B := False; A := B and Func;Here Func is a function which returns a Boolean type.
Remark: The WordBool, LongBool and ByteBool types were not supported by Free Pascal until version 0.99.6.
(see chapter Expressions for how to use expressions) When using assigned enumerated types, the assigned elements must be in ascending numerical order in the list, or the compiler will complain. The expressions used in assigned enumerated elements must be known at compile time. So the following is a correct enumerated type declaration:
Enumerated types
Type Direction = ( North, East, South, West );The C style enumeration type looks as follows:
Type EnumType = (one, two, three, forty := 40,fortyone);As a result, the ordinal number of forty is 40, and not 3, as it would be when the ':= 40' wasn't present. The ordinal value of fortyone is then 41, and not 4, as it would be when the assignment wasn't present. After an assignment in an enumerated definition the compiler adds 1 to the assigned value to assign to the next enumerated value. When specifying such an enumeration type, it is important to keep in mind that you should keep the enumerated elements in ascending order. The following will produce a compiler error:
Type EnumType = (one, two, three, forty := 40, thirty := 30);It is necessary to keep forty and thirty in the correct order. When using enumeration types it is important to keep the following points in mind:
Type LargeEnum = ( BigOne, BigTwo, BigThree ); {$PACKENUM 1} SmallEnum = ( one, two, three ); Var S : SmallEnum; L : LargeEnum; begin WriteLn ('Small enum : ',SizeOf(S)); WriteLn ('Large enum : ',SizeOf(L)); end.will, when run, print the following:
Small enum : 1 Large enum : 4
Some of the predefined integer types are defined as subrange types:
Subrange types
Type Longint = $80000000..$7fffffff; Integer = -32768..32767; shortint = -128..127; byte = 0..255; Word = 0..65535;But you can also define subrange types of enumeration types:
Type Days = (monday,tuesday,wednesday,thursday,friday, saturday,sunday); WorkDays = monday .. friday; WeekEnd = Saturday .. Sunday;
Type | Range | Significant digits | Size3.2 |
Single | 1.5E-45 .. 3.4E38 | 7-8 | 4 |
Real | 5.0E-324 .. 1.7E308 | 15-16 | 8 |
Double | 5.0E-324 .. 1.7E308 | 15-16 | 8 |
Extended | 1.9E-4951 .. 1.1E4932 | 19-20 | 10 |
Comp | -2E64+1 .. 2E63-1 | 19-20 | 8 |
^
) can be used in combination with a letter to
specify a character with ASCII value less than 27. Thus ^G
equals
#7 (G is the seventh letter in the alphabet.)
If you want to represent the single quote character, type it two times
successively, thus '''' represents the single quote character.
ShortString
The meaning of a string declaration statement is interpreted differently depending on the {$H} switch. The above declaration can declare an ansistrng or a short string.
Whatever the actual type, ansistrings and short strings can be used interchangeably. The compiler always takes care of the necessary type coversions. Note, however, that the result of an expression that contains ansistrings and short strings will always be an ansistring.
A string declaration declares a short string in the following cases:
The predefined type ShortString is defined as a string of length 255:
ShortString = String[255];
For short strings Free Pascal reserves Size+1 bytes for the string S, and in the zeroeth element of the string (S[0]) it will store the length of the variable. If you don't specify the size of the string, 255 is taken as a default. For example in
{$H-} Type NameString = String[10]; StreetString = String;NameString can contain maximum 10 characters. While StreetString can contain 255 characters. The sizes of these variables are, respectively, 11 and 256 bytes.
If the {$H} switch is on, then a string definition that doesn't contain a length specifier, will be regarded as an ansistring.
Ansistrings are strings that have no length limit. They are reference counted. Internally, an ansistring is treated as a pointer.
If the string is empty (''), then the pointer is nil. If the string is not empty, then the pointer points to a structure in heap memory that looks as in table (ansistrings) .
Because of this structure, it is possible to typecast an ansistring to a pchar. If the string is empty (so the pointer is nil) then the compiler makes sure that the typecasted pchar will point to a null byte.
AnsiStrings can be unlimited in length. Since the length is stored, the length of an ansistring is available immediatly, providing for fast access.
Assigning one ansistring to another doesn't involve moving the actual string. A statement
S2:=S1;results in the reference count of S2 being decreased by one, The referece count of S1 is increased by one, and finally S1 (as a pointer) is copied to S2. This is a significant speed-up in your code.
If a reference count reaches zero, then the memory occupied by the string is deallocated automatically, so no memory leaks arise.
When an ansistring is declared, the Free Pascal compiler initially allocates just memory for a pointer, not more. This pointer is guaranteed to be nil, meaning that the string is initially empty. This is true for local, global or part of a structure (arrays, records or objects).
This does introduce an overhead. For instance, declaring
Var A : Array[1..100000] of string;Will copy 100,000 times nil into A. When A goes out of scope, then the 100,000 strings will be dereferenced one by one. All this happens invisibly for the programmer, but when considering performance issues, this is important.
Memory will be allocated only when the string is assigned a value. If the string goes out of scope, then it is automatically dereferenced.
If you assign a value to a character of a string that has a reference count greater than 1, such as in the following statements:
S:=T; { reference count for S and T is now 2 } S[I]:='@';then a copy of the string is created before the assignment. This is known as copy-on-write semantics.
It is impossible to access the length of an ansistring by referring to the zeroeth character. The following statement will generate a compiler error if S is an ansistring:
Len:=S[0];Instead, you must use the Length function to get the length of a string.
To set the length of an ansistring, you can use the SetLength function. Constant ansistrings have a reference count of -1 and are treated specially.
Ansistrings are converted to short strings by the compiler if needed, this means that you can mix the use of ansistrings and short strings without problems.
You can typecast ansistrings to PChar or Pointer types:
Var P : Pointer; PC : PChar; S : AnsiString; begin S :='This is an ansistring'; PC:=Pchar(S); P :=Pointer(S);There is a difference between the two typecasts. If you typecast an empty ansistring to a pointer, the pointer wil be Nil. If you typecast an empty ansistring to a PChar, then the result will be a pointer to a zero byte (an empty string).
The result of such a typecast must be used with care. In general, it is best to consider the result of such a typecast as read-only, i.e. suitable for passing to a procedure that needs a constant pchar argument.
It is therefore NOT advisable to typecast one of the following:
To specify a constant string, you enclose the string in single-quotes, just as a Char type, only now you can have more than one character. Given that S is of type String, the following are valid assignments:
S := 'This is a string.'; S := 'One'+', Two'+', Three'; S := 'This isn''t difficult !'; S := 'This is a weird character : '#145' !';As you can see, the single quote character is represented by 2 single-quote characters next to each other. Strange characters can be specified by their ASCII value. The example shows also that you can add two strings. The resulting string is just the concatenation of the first with the second string, without spaces in between them. Strings can not be substracted, however.
Whether the constant string is stored as an ansistring or a short string depends on the settings of the {$H} switch.
program one; var p : PChar; begin P := 'This is a null-terminated string.'; WriteLn (P); end.Results in the same as
program two; const P : PChar = 'This is a null-terminated string.' begin WriteLn (P); end.These examples also show that it is possible to write the contents of the string to a file of type Text. The strings unit contains procedures and functions that manipulate the PChar type as you can do it in C. Since it is equivalent to a pointer to a type Char variable, it is also possible to do the following:
Program three; Var S : String[30]; P : PChar; begin S := 'This is a null-terminated string.'#0; P := @S[1]; WriteLn (P); end.This will have the same result as the previous two examples. You cannot add null-terminated strings as you can do with normal Pascal strings. If you want to concatenate two PChar strings, you will need to use the unit strings. However, it is possible to do some pointer arithmetic. You can use the operators + and - to do operations on PChar pointers. In table (PCharMath) , P and Q are of type PChar, and I is of type Longint.
Operation | Result |
P + I | Adds I to the address pointed to by P. |
I + P | Adds I to the address pointed to by P. |
P - I | Substracts I from the address pointed to by P. |
P - Q | Returns, as an integer, the distance between 2 addresses |
(or the number of characters between P and Q) |
Unlike Delphi, Free Pascal does not support the keyword Packed for all structured types, as can be seen in the syntax diagram. It will be mentioned when a type supports the packed keyword. In the following, each of the possible structured types is discussed.
Structured Types
The following is a valid array declaration:
Array types
Type RealArray = Array [1..100] of Real;As in Turbo Pascal, if the array component type is in itself an array, it is possible to combine the two arrays into one multi-dimensional array. The following declaration:
Type APoints = array[1..100] of Array[1..3] of Real;is equivalent to the following declaration:
Type APoints = array[1..100,1..3] of Real;The functions High and Low return the high and low bounds of the leftmost index type of the array. In the above case, this would be 100 and 1.
So the following are valid record types declarations:
Record types
Type Point = Record X,Y,Z : Real; end; RPoint = Record Case Boolean of False : (X,Y,Z : Real); True : (R,theta,phi : Real); end; BetterRPoint = Record Case UsePolar : Boolean of False : (X,Y,Z : Real); True : (R,theta,phi : Real); end;The variant part must be last in the record. The optional identifier in the case statement serves to access the tag field value, which otherwise would be invisible to the programmer. It can be used to see which variant is active at a certain time. In effect, it introduces a new field in the record.
Remark: It is possible to nest variant parts, as in:
Type MyRec = Record X : Longint; Case byte of 2 : (Y : Longint; case byte of 3 : (Z : Longint); ); end;
The size of a record is the sum of the sizes of its fields, each size of a field is rounded up to a power of two. If the record contains a variant part, the size of the variant part is the size of the biggest variant, plus the size of the tag field type if an identifier was declared for it. Here also, the size of each part is first rounded up to two. So in the above example, SizeOf would return 24 for Point, 24 for RPoint and 26 for BetterRPoint. For MyRec, the value would be 12. If you want to read a typed file with records, produced by a Turbo Pascal program, then chances are that you will not succeed in reading that file correctly. The reason for this is that by default, elements of a record are aligned at 2-byte boundaries, for performance reasons. This default behaviour can be changed with the {$PackRecords n} switch. Possible values for n are 1, 2, 4, 16 or Default. This switch tells the compiler to align elements of a record or object or class that have size larger than n on n byte boundaries. Elements that have size smaller or equal than n are aligned on natural boundaries, i.e. to the first power of two that is larger than or equal to the size of the record element. The keyword Default selects the default value for the platform you're working on (currently, this is 2 on all platforms) Take a look at the following program:
Program PackRecordsDemo; type {$PackRecords 2} Trec1 = Record A : byte; B : Word; end; {$PackRecords 1} Trec2 = Record A : Byte; B : Word; end; {$PackRecords 2} Trec3 = Record A,B : byte; end; {$PackRecords 1} Trec4 = Record A,B : Byte; end; {$PackRecords 4} Trec5 = Record A : Byte; B : Array[1..3] of byte; C : byte; end; {$PackRecords 8} Trec6 = Record A : Byte; B : Array[1..3] of byte; C : byte; end; {$PackRecords 4} Trec7 = Record A : Byte; B : Array[1..7] of byte; C : byte; end; {$PackRecords 8} Trec8 = Record A : Byte; B : Array[1..7] of byte; C : byte; end; Var rec1 : Trec1; rec2 : Trec2; rec3 : TRec3; rec4 : TRec4; rec5 : Trec5; rec6 : TRec6; rec7 : TRec7; rec8 : TRec8; begin Write ('Size Trec1 : ',SizeOf(Trec1)); Writeln (' Offset B : ',Longint(@rec1.B)-Longint(@rec1)); Write ('Size Trec2 : ',SizeOf(Trec2)); Writeln (' Offset B : ',Longint(@rec2.B)-Longint(@rec2)); Write ('Size Trec3 : ',SizeOf(Trec3)); Writeln (' Offset B : ',Longint(@rec3.B)-Longint(@rec3)); Write ('Size Trec4 : ',SizeOf(Trec4)); Writeln (' Offset B : ',Longint(@rec4.B)-Longint(@rec4)); Write ('Size Trec5 : ',SizeOf(Trec5)); Writeln (' Offset B : ',Longint(@rec5.B)-Longint(@rec5), ' Offset C : ',Longint(@rec5.C)-Longint(@rec5)); Write ('Size Trec6 : ',SizeOf(Trec6)); Writeln (' Offset B : ',Longint(@rec6.B)-Longint(@rec6), ' Offset C : ',Longint(@rec6.C)-Longint(@rec6)); Write ('Size Trec7 : ',SizeOf(Trec7)); Writeln (' Offset B : ',Longint(@rec7.B)-Longint(@rec7), ' Offset C : ',Longint(@rec7.C)-Longint(@rec7)); Write ('Size Trec8 : ',SizeOf(Trec8)); Writeln (' Offset B : ',Longint(@rec8.B)-Longint(@rec8), ' Offset C : ',Longint(@rec8.C)-Longint(@rec8)); end.The output of this program will be :
Size Trec1 : 4 Offset B : 2 Size Trec2 : 3 Offset B : 1 Size Trec3 : 2 Offset B : 1 Size Trec4 : 2 Offset B : 1 Size Trec5 : 8 Offset B : 4 Offset C : 7 Size Trec6 : 8 Offset B : 4 Offset C : 7 Size Trec7 : 12 Offset B : 4 Offset C : 11 Size Trec8 : 16 Offset B : 8 Offset C : 15And this is as expected. In Trec1, since B has size 2, it is aligned on a 2 byte boundary, thus leaving an empty byte between A and B, and making the total size 4. In Trec2, B is aligned on a 1-byte boundary, right after A, hence, the total size of the record is 3. For Trec3, the sizes of A,B are 1, and hence they are aligned on 1 byte boundaries. The same is true for Trec4. For Trec5, since the size of B - 3 - is smaller than 4, B will be on a 4-byte boundary, as this is the first power of two that is larger than it's size. The same holds for Trec6. For Trec7, B is aligned on a 4 byte boundary, since it's size - 7 - is larger than 4. However, in Trec8, it is aligned on a 8-byte boundary, since 8 is the first power of two that is greater than 7, thus making the total size of the record 16. As from version 0.9.3, Free Pascal supports also the 'packed record', this is a record where all the elements are byte-aligned. Thus the two following declarations are equivalent:
{$PackRecords 1} Trec2 = Record A : Byte; B : Word; end; {$PackRecords 2}and
Trec2 = Packed Record A : Byte; B : Word; end;Note the {$PackRecords 2} after the first declaration !
Each of the elements of SetType must be of type TargetType. TargetType can be any ordinal type with a range between 0 and 255. A set can contain maximally 255 elements. The following are valid set declaration:
Set Types
Type Junk = Set of Char; Days = (Mon, Tue, Wed, Thu, Fri, Sat, Sun); WorkDays : Set of days;Given this set declarations, the following assignment is legal:
WorkDays := [ Mon, Tue, Wed, Thu, Fri];The operators and functions for manipulations of sets are listed in table (SetOps) .
If no type identifier is given, then the file is an untyped file; it can be considered as equivalent to a file of bytes. Untyped files require special commands to act on them (see Blockread, Blockwrite). The following declaration declares a file of records:
File types
Type Point = Record X,Y,Z : real; end; PointFile = File of Point;Internally, files are represented by the FileRec record, which is declared in the DOS unit.
A special file type is the Text file type, represented by the TextRec record. A file of type Text uses special input-output routines.
As can be seen from this diagram, pointers are typed, which means that they point to a particular kind of data. The type of this data must be known at compile time. Dereferencing the pointer (denoted by adding ^ after the variable name) behaves then like a variable. This variable has the type declared in the pointer declaration, and the variable is stored in the address that is pointed to by the pointer variable. Consider the following example:
Pointer types
Program pointers; type Buffer = String[255]; BufPtr = ^Buffer; Var B : Buffer; BP : BufPtr; PP : Pointer; etc..In this example, BP is a pointer to a Buffer type; while B is a variable of type Buffer. B takes 256 bytes memory, and BP only takes 4 bytes of memory (enough to keep an adress in memory).
Remark: Free Pascal treats pointers much the same way as C does. This means that you can treat a pointer to some type as being an array of this type. The pointer then points to the zeroeth element of this array. Thus the following pointer declaration
Var p : ^Longint;Can be considered equivalent to the following array declaration:
Var p : array[0..Infinity] of Longint;The difference is that the former declaration allocates memory for the pointer only (not for the array), and the second declaration allocates memory for the entire array. If you use the former, you must allocate memory yourself, using the Getmem function. The reference P^ is then the same as p[0]. The following program illustrates this maybe more clear:
program PointerArray; var i : Longint; p : ^Longint; pp : array[0..100] of Longint; begin for i := 0 to 100 do pp[i] := i; { Fill array } p := @pp[0]; { Let p point to pp } for i := 0 to 100 do if p[i]<>pp[i] then WriteLn ('Ohoh, problem !') end.
Free Pascal supports pointer arithmetic as C does. This means that, if P is a typed pointer, the instructions
Inc(P); Dec(P);Will increase, respectively descrease the address the pointer points to with the size of the type P is a pointer to. For example
Var P : ^Longint; ... Inc (p);will increase P with 4. You can also use normal arithmetic operators on pointers, that is, the following are valid pointer arithmetic operations:
var p1,p2 : ^Longint; L : Longint; begin P1 := @P2; P2 := @L; L := P1-P2; P1 := P1-4; P2 := P2+4; end.Here, the value that is added or substracted is not multiplied by the size of the type the pointer points to.
For a description of formal parameter lists, see chapter Procedures. The two following examples are valid type declarations:
Procedural types
Type TOneArg = Procedure (Var X : integer); TNoArg = Function : Real; var proc : TOneArg; func : TNoArg;One can assign the following values to a procedural type variable:
Procedure printit (Var X : Integer); begin WriteLn (x); end; ... Proc := @printit; Func := @Pi;From this example, the difference with Turbo Pascal is clear: In Turbo Pascal it isn't necessary to use the address operator (@) when assigning a procedural type variable, whereas in Free Pascal it is required (unless you use the -So switch, in which case you can drop the address operator.)
Remark: The modifiers concerning the calling conventions (cdecl, pascal, stdcall and popstack stick to the declaration; i.e. the following code would give an error:
Type TOneArgCcall = Procedure (Var X : integer);cdecl; var proc : TOneArgCcall; Procedure printit (Var X : Integer); begin WriteLn (x); end; begin Proc := @printit; end.Because the TOneArgCcall type is a procedure that uses the cdecl calling convention.