12.12 Variant Dynamic Records

If one has a variant record (see Section 11.7) that contains one or more tag fields and their variants call for differing amounts of memory, then the statements

SIZE (thePoint);
NEW (thePoint);

where thePoint is a pointer to such a record will necessarily report and reserve (respectively) the maximum number of memory locations possible for the variant.

However, if there are large differences in memory requirements for the variants, it might be useful to reserve only the amount necessary. There is a variant (sic) version of NEW to do just this; one places after the pointer parameter the values of each tag field in turn. For instance, given the declarations:

  Item = POINTER TO ItemData;
  ItemData =
      number : CARDINAL;
      CASE tag : BOOLEAN OF
        TRUE :  
          married : BOOLEAN |
        FALSE :  
          num1, num2, num3 : CARDINAL
  point1, point2, point3: Item;

a call to NEW (point1) reserves the maximum possible number of locations (for number, tag, num1, num2, and num3,) but a call to

NEW (point2, TRUE);

reserves only the number of locations needed for number, tag, and married. A call to

NEW (point2, FALSE); 

reserves the number of locations needed for number, tag, num1, num2, and num3.

If there are more variant parts, the value of each tag field in turn is listed. Exactly the same considerations apply to DISPOSE. If the user wishes to know the number of locations used by a variant, the procedure SIZE will not, as indicated above, report the correct value. In order to make the computation, it is necessary to import the procedure SYSTEM.TSIZE. This procedure has the same syntax as the variant forms of NEW and DISPOSE except that its first parameter is always a type rather than a variable.

To illustrate further, suppose one has:

  Item = POINTER TO ItemData;
    ItemData =
        int : INTEGER;
        CASE b : BOOLEAN OF
          TRUE : 
            sex : BOOLEAN |
          FALSE : 
            num1, num2 : INTEGER
        CASE descriptor : CARDINAL OF
          1 :  
            flag : BOOLEAN|
          2..5 :  
            int2 : INTEGER
            num3, num4, num5 : INTEGER
  point : Item;

Now, one may write things like:

  NEW (point1);
  WriteString ("size of point1^ is ");
  WriteCard (SIZE (point1^), 6);
  NEW (point2, FALSE);
  WriteString ("tsize of point2^ is ");
  WriteCard (TSIZE (ItemData, FALSE), 6);
  WriteString (" note that size of point2^ is ");
  WriteCard (SIZE (point2^), 6);

  NEW (point3, TRUE);
  WriteString ("tsize of point3^ is ");
  WriteCard (TSIZE (ItemData, TRUE), 6);

When this code was run on one machine, the results were as follows:

size of point1^ is     18
tsize of point2^ is     18 note that size of point2^ is     18
tsize of point3^ is      6 note that size of point3^ is     18

Observe that SIZE always gives the maximum number of locations whereas TSIZE gives the number appropriate to the particular variant. The ISO standard says that calls to

NEW (p, <expression list>);
DISPOSE (p, <expression list>);

are translated into calls to


respectively. If the program makes a version of ALLOCATE and DEALLOCATE available other than that supplied by Storage, these calls will work in the same way as the standard (one parameter) calls to NEW and DISPOSE. That is, all the "magic" (varying number of parameters) is in TSIZE and that is why it is located in SYSTEM.

NOTES: 1. For this variation on memory allocation to work correctly, it is essential that a variable's tag fields be correctly initialized and remain the same once it is allocated, so that when DISPOSE is called, the list of constants is the same as when memory was allocated by NEW.
2. Some Modula-2 compilers can only assign the maximum amount of space to variant records, so much of this discussion may not apply to them. They would always give the same value when the above code was run.
3. There can be difficulties with writing such records out to the disk and later reading back in, as there must be a way of determining the variants before the NEW statement is executed. Moreover, the random access file scheme depends on all records having the same size, and could not be used with variants of different sizes.

In addition, when using TSIZE, if there is more than one parameter, the first must be a record. That is, TSIZE will work on any type, but it makes no sense to supply a type that is not a variant record, and then supply additional parameters that can only be tag field values. The compiler will flag this error. The values for the fields of variant records may also be included as parameters following the record type. TSIZE then returns the size of the specified variant.

For any use of TSIZE, whether translated form a call to NEW or not, if there are two or more variant fields, the values of the various tag fields are listed in the same order they are declared.

NOTES: 1. In some non-ISO versions, this facility is limited to one main variant, which must be at the end of the RECORD. Additional variant tags can be listed only if they are nested within the first.

2. In some non-ISO versions, SIZE and TSIZE both return the number of words. This must be doubled or possibly quadrupled to obtain the number of storage locations (often called bytes on such systems).

3. The tag fields whose values are given must be fields of the main record type itself, and not fields of a sub-record type.

4. The time at which these functions operate is also worth noting. TSIZE operates strictly on entities whose size is known to the compiler, and so does SIZE most of the time. Therefore, these calculations are done by the compiler, not while the finished program is running. There is one exception, but only in non-ISO versions. When SIZE operates on an open array, the size is not known until the assignment has been made during the program's execution. So in such cases instance, SIZE is a run-time procedure. The ISO standard simply does not allow one to take the SIZE of an open array.

The restrictions on SIZE and TSIZE in the ISO version ensure that both can be used in constant expressions and can be evaluated at compile time.