Data formats
This section gives information on the storage space occupied by the different possible types in Free Pascal. Information on internal alignment will also be given.
Integer types
The storage size of the default integer types are given in Language Reference. In the case of user defined-types, the storage space occupied depends on the bounds of the type:
If both bounds are within range -128..127, the variable is stored as a shortint (signed 8-bit quantity).
If both bounds are within the range 0..255, the variable is stored as a byte (unsigned 8-bit quantity).
If both bounds are within the range -32768..32767, the variable is stored as a smallint (signed 16-bit quantity).
If both bounds are within the range 0..65535, the variable is stored as a word (unsigned 16-bit quantity)
If both bounds are within the range 0..4294967295, the variable is stored as a longword (unsigned 32-bit quantity).
Otherwise the variable is stored as a longint (signed 32-bit quantity).
Char types
A char, or a subrange of the char type, is stored as a byte. A WideChar is stored as a word, i.e. 2 bytes.
Boolean types
The Boolean type is stored as a byte and can take a value of true or false.
A ByteBool is stored as a byte, a WordBool type is stored as a word, and a longbool is stored as a longint.
Enumeration types
By default all enumerations are stored as a longword (4 bytes), which is equivalent to specifying the {$Z4}, {$PACKENUM 4} or {$PACKENUM DEFAULT} switches.
This default behavior can be changed by compiler switches, and by the compiler mode.
In the tp compiler mode, or while the {$Z1} or {$PACKENUM 1} switches are in effect, the storage space used is shown in table Memory issues.
| # Of Elements in Enum. | Storage space used |
|---|---|
| 0..255 | byte (1 byte) |
| 256..65535 | word (2 bytes) |
| > 65535 | longword (4 bytes) |
When the {$Z2} or {$PACKENUM 2} switches are in effect, the value is stored in 2 bytes (a word), if the enumeration has less or equal than 65535 elements. If there are more elements, the enumeration value is stored as a 4 byte value (a longword).
It is important to note that the number of elements is determined by the highest value of the enumeration. If the enumeration has assigned values, then the "number of elements" is actually the highest value in the enumeration+1.
program Project1;
{$PACKENUM 2}
type
TEnum = (a, b, c = $FFFF + 1);
begin
writeln(sizeof(TEnum)); // 4
end.
will print 4 because the number of elements is $FFFF+2
Floating point types
Floating point type sizes and mapping vary from one processor to another. Except for the Intel 80x86 architecture, the extended type maps to the IEEE double type if a hardware floating point coprocessor is present.
Floating point types have a storage binary format divided into three distinct fields : the mantissa, the exponent and the sign bit which stores the sign of the floating point value.
Single
The single type occupies 4 bytes of storage space, and its memory structure is the same as the IEEE-754 single type. This type is the only type which is guaranteed to be available on all platforms that have hardware or software emulation. This means that the AVR platform for example does not have this type.
The memory format of the single format looks like what is shown in figure [fig:single].
Double
The double type occupies 8 bytes of storage space, and its memory structure is the same as the IEEE-754 double type.
The memory format of the double format looks like like what is shown in figure [fig:double].
On processors which do not support co-processor operations (and which have the {$E+} switch), the double type does not exist.
Extended
For Intel 80x86 processors, the extended type takes up 10 bytes of memory space. For more information on the extended type, consult the Intel Programmer’s reference.
For all other processors which support floating point operations, the extended type is a nickname for the type which supports the most precision, this is usually the double type. On processors which do not support co-processor operations (and which have the {$E+} switch), the extended type usually maps to the single type.
Comp
For Intel 80x86 processors, the comp type contains a 63-bit integral value, and a sign bit (in the MSB position). The comp type uses 8 bytes of storage space.
On other processors, the comp type is not supported.
Real
Contrary to Turbo Pascal, where the real type had a special internal format, under Free Pascalthe real type simply maps to one of the other real types. It maps to the double type on processors which support floating point operations, while it maps to the single type on processors which do not support floating point operations in hardware. See table Memory issues for more information on this.
| Processor | Real type mapping |
|---|---|
| Intel 80x86 | double |
| Motorola 680x0 (with {$E-} switch) | double |
| Motorola 680x0 (with {$E+} switch) | single |
Pointer types
A pointer type is stored as a longword (unsigned 32-bit value) on 32-bit processors, and is stored as a 64-bit unsigned value6 on 64-bit processors.
String types
Ansistring types
The ansistring is a dynamically allocated string which has no length limitation (other than addressable memory). When the string is no longer being referenced (its reference count reaches zero), its memory is automatically freed.
If the ansistring is a constant, then its reference count will be equal to -1, indicating that it should never be freed. The structure in memory for an ansistring is shown in table Memory issues for 32-bit programs, and table Memory issues for 64-bit programs
| Offset | Contains |
|---|---|
| -12 | Code page indicator (2 bytes). |
| -10 | Character size (2 bytes) |
| -8 | Longint with reference count. |
| -4 | Longint with actual string size. |
| 0 | Actual array of char, null-terminated. |
| Offset | Contains |
|---|---|
| -24 | Code page indicator (2 bytes). |
| -22 | Character size (2 bytes) |
| -16 | Sizeint with reference count. |
| -8 | Sizeint with actual string size. |
| 0 | Actual array of char, null-terminated. |
Shortstring types
A shortstring occupies as many bytes as its maximum length plus one. The first byte contains the current dynamic length of the string. The following bytes contain the actual characters (of type char) of the string. The maximum size of a short string is the length byte followed by 255 characters.
Unicodestring types
A unicode string is allocated on the heap, much like an ansistring. Unlike the ansistring, a unicodestring takes 2 bytes per character, and is terminated with a double null. It is reference counted just like an ansistring. The structure in memory for a unicodestring is shown in table Memory issues for 32-bit programs, and table Memory issues for 64-bit programs
| Offset | Contains |
|---|---|
| -8 | Longint with reference count. |
| -4 | Longint with actual string size. |
| 0 | Actual array of char, null-terminated. |
| Offset | Contains |
|---|---|
| -16 | SizeInt with reference count. |
| -8 | SizeInt with actual string size. |
| 0 | Actual array of char, null-terminated. |
Widestring types
On Non-Windows platforms, the widestring type is equal to the unicode string type.
On Windows platforms, a widestring is allocated on the heap, much like a unicode string, and is terminated with a double null. In difference with a unicode string, it is not reference counted.
Set types
A set is stored as an array of bits, where each bit indicates if the element is in the set or excluded from the set. The maximum number of elements in a set is 256.
How many bytes the compiler uses to store a sets depends on the mode. For non-Delphi modes, the compiler stores small sets (less than 32 elements) in a Longint, if the set element type range allows it. This allows for faster processing and decreases program size. Otherwise, sets are stored in 32 bytes.
For Delphi mode, the compiler tries to store the set in the smallest number of bytes in which the set will fit.
The following program illustrates this:
Type
TSet1 = Set of 0..1;
TSet2 = Set of 0..8;
TSet3 = Set of 0..16;
TSet4 = Set of 0..128;
TSet5 = Set of 0..247;
TSet6 = Set of 0..255;
begin
Writeln('Set 1: ',SizeOf(TSet1));
Writeln('Set 2: ',SizeOf(TSet2));
Writeln('Set 3: ',SizeOf(TSet3));
Writeln('Set 4: ',SizeOf(TSet4));
Writeln('Set 5: ',SizeOf(TSet5));
Writeln('Set 6: ',SizeOf(TSet6));
end.
When compiled in fpc mode, the program outputs the following:
Set 1: 4 Set 2: 4 Set 3: 4 Set 4: 32 Set 5: 32 Set 6: 32
In Delphi mode, the program will print:
Set 1: 1 Set 2: 2 Set 3: 4 Set 4: 17 Set 5: 31 Set 6: 32
Note that the set element size can be influenced with the {$PACKSET } directive, section Compiler directives.
The longword number of a specific element E is given by :
LongwordNumber = (E div 32);
and the bit number within that 32-bit value is given by:
BitNumber = (E mod 32);
Static array types
A static array is stored as a contiguous sequence of variables of the components of the array. The components with the lowest indexes are stored first in memory. No alignment is done between each element of the array. A multi-dimensional array is stored with the rightmost dimension increasing first (row-major order).
Dynamic array types
A dynamic array is stored as a pointer to a block of memory on the heap. The memory on the heap is a contiguous sequence of variables of the components of the array, just as for a static array. The reference count and memory size are stored in memory just before the actual start of the array, at a negative offset relative to the address the pointer refers to. It should not be used.
Record types
Each field of a record is stored in a contiguous sequence of variables, where the first field is stored at the lowest address in memory. In case of variant fields in a record, each variant starts at the same address in memory. Fields of a record are usually aligned, unless the packed directive is specified when declaring the record type.
For more information on field alignment, consult section Memory issues.
Object types
Objects are stored in memory just as ordinary records with an extra field: a pointer to the Virtual Method Table (VMT). This field is stored first, and all fields in the object are stored in the order they are declared (with possible alignment of field addresses, unless the object was declared as being packed).
The VMT is initialized by the call to the object’s Constructor method. If the new operator was used to call the constructor, the data fields of the object will be stored in heap memory, otherwise they will directly be stored in the data section of the final executable.
If an object doesn’t have virtual methods, no pointer to a VMT is inserted.
The memory allocated looks as in table Memory issues.
| Offset | What | |
| 32-bit | 64-bit | |
| +0 | +0 | Pointer to VMT (optional). |
| +4 | +8 | Data. All fields in the order they’ve been declared. |
| … |
The Virtual Method Table (VMT) for each object type consists of 2 check fields (containing the size of the data), a pointer to the object’s ancestor’s VMT (Nil if there is no ancestor), and then the pointers to all virtual methods. The VMT layout is illustrated in table Memory issues. The VMT is constructed by the compiler.
| Offset | What | |
| 32-bit | 64-bit | |
| +0 | +0 | Size of object type data |
| +4 | +8 | Minus the size of object type data. Enables determining of valid VMT pointers. |
| +8 | +16 | Pointer to ancestor VMT, Nil if no ancestor available. |
| +12 | +24 | Pointers to the virtual methods. |
| … |
Class types
Just like objects, classes are stored in memory just as ordinary records with an extra field: a pointer to the Virtual Method Table (VMT). This field is stored first, and all fields in the class are stored, not necessarily in the order they are declared: the compiler may rearrange the fields to better pack them.
Contrary to objects, all data fields of a class are always stored in heap memory.
The memory allocated looks as in table Memory issues.
| Offset | What | |
| 32-bit | 64-bit | |
| +0 | +0 | Pointer to VMT. |
| +4 | +8 | Data. All fields in the order they’ve been arranged by the compiler. |
| … |
The Virtual Method Table (VMT) of each class consists of several fields, which are used for runtime type information. The VMT layout is illustrated in table Memory issues. The VMT is constructed by the compiler.
| Offset | What | |
| 32-bit | 64-bit | |
| +0 | +0 | Size of object type data |
| +4 | +8 | Minus the size of object type data. Enables determining of valid VMT pointers. |
| +8 | +16 | Pointer to ancestor VMT, Nil if no ancestor available. |
| +12 | +24 | Pointer to the class name (stored as a shortstring). |
| +16 | +32 | Pointer to the dynamic method table (using message with integers). |
| +20 | +40 | Pointer to the method definition table. |
| +24 | +48 | Pointer to the field definition table. |
| +28 | +56 | Pointer to type information table. |
| +32 | +64 | Pointer to instance initialization table. |
| +36 | +72 | Pointer to the auto table. |
| +40 | +80 | Pointer to the interface table. |
| +44 | +88 | Pointer to the dynamic method table (using message with strings). |
| +48 | +96 | Pointer to the Destroy destructor. |
| +52 | +104 | Pointer to the NewInstance method. |
| +56 | +112 | Pointer to the FreeInstance method. |
| +60 | +120 | Pointer to the SafeCallException method. |
| +64 | +128 | Pointer to the DefaultHandler method. |
| +68 | +136 | Pointer to the AfterConstruction method. |
| +72 | +144 | Pointer to the BeforeDestruction method. |
| +76 | +152 | Pointer to the DefaultHandlerStr method. |
| +80 | +160 | Pointer to the Dispatch method. |
| +84 | +168 | Pointer to the DispatchStr method. |
| +88 | +176 | Pointer to the Equals method. |
| +92 | +184 | Pointer to the GetHashCode method. |
| +96 | +192 | Pointer to the ToString method. |
| +100 | +200 | Pointers to other virtual methods. |
| … |
See the file rtl/inc/objpash.inc for the most current VMT information.
File types
File types are represented as records. Typed files and untyped files are represented as a fixed record:
Const
PrivDataLength=3*SizeOf(SizeInt) + 5*SizeOf(pointer);
Type
filerec = packed record
handle : THandle;
mode : longint;
recsize : Sizeint;
_private : array[1..PrivDataLength] of byte;
userdata : array[1..32] of byte;
name : array[0..filerecnamelength] of char;
End;
Text files are described using the following record:
TextBuf = array[0..255] of char;
textrec = packed record
handle : THandle;
mode : longint;
bufsize : SizeInt;
_private : SizeInt;
bufpos : SizeInt;
bufend : SizeInt;
bufptr : ^textbuf;
openfunc : pointer;
inoutfunc : pointer;
flushfunc : pointer;
closefunc : pointer;
userdata : array[1..32] of byte;
name : array[0..255] of char;
LineEnd : TLineEndStr;
buffer : textbuf;
End;
- handle
-
The handle field returns the file handle (if the file is opened), as returned by the operating system.
- mode
-
The mode field can take one of several values. When it is
fmclosed, then the file is closed, and thehandlefield is invalid. When the value is equal tofminput, it indicates that the file is opened for read only access.fmoutputindicates write only access, and thefminoutindicates read-write access to the file. - name
-
The
namefield is a null terminated character string representing the name of the file. - userdata
-
The
userdatafield is never used by Free Pascalfile handling routines, and can be used for special purposes by software developers.
Procedural types
A procedural type is stored as a generic pointer, which stores the address of the routine.
A procedural type to a normal procedure or function is stored as a generic pointer, which stores the address of the entry point of the routine.
In the case of a method procedural type, the storage consists of two pointers, the first being a pointer to the entry point of the method, and the second one being a pointer to self (the object instance).
An experiment, built from development snapshots. Not the official Free Pascal or Lazarus documentation.
