3.3.2 Record types

Free Pascal supports xed records and records with variant parts. The syntax diagram for a record type is

_________________________________________________________________________________________________________ Record types
-- --record type-|---------record--|---------end --------------------
                -packed --        -  eld list--

-- --  eld list ---|------  xed  elds -----------|-------------------------
             --|-------------variant part-- -;--
               -  xed   elds-; --

-- --  xed  elds---identi  er list :-type---------------------------------
               6-------- ;----------

-- --variant part case----------------ordinal type identi  er of-variant----
                     -identi  er :-|                       6--;----|

-- --      -|-      -  --- -  ------------ ------------------------
     variant 6-constant--,-| :  (  -      --|)
                                   eld list
___________________________________________________________________

So the following are valid record types declarations:

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 identier in the case statement serves to access the tag eld value, which otherwise would be invisible to the programmer. It can be used to see which variant is active at a certain time. In eect, it introduces a new eld 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 elds, each size of a eld 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 eld type if an identier was declared for it. Here also, the size of each part is rst 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 a typed le with records, produced by a Turbo Pascal program, must be read, then chances are that attempting to read that le correctly will fail. 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 f$PackRecords ng 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 rst 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 that the code is compiled for (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 : 15

And 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 rst 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 rst power of two that is greater than 7, thus making the total size of the record 16. 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 f$PackRecords 2g after the rst declaration !