previous next top complete contents complete index framed top this page unframed
For example, a record with three fields, a STRING named str, an INTEGER named base, and another INTEGER named val, may be imagined as three adjacent boxes, the first holding the value of the field str, the second holding the value of base, and the third holding the value of val. If str = "hello", base = 53, and val = 28, then the record may be pictured as:
Records are allocated and accessed in two different ways, each using its own syntax:
Figure 10–2 shows how a POINTER variable p pointing at a dynamic record is illustrated in diagrams; Figure 10–3 shows an inplace record variable r containing the same fields is illustrated.
Dynamic and inplace records are similar in many ways; they differ mainly in the ways in which they are created and destroyed. In MAINSAIL documentation, the term “record” can mean either an inplace record or a dynamic record, unless the context makes it clear that only one kind of record is intended.
A field of a record is accessed by means of a field variable (as described in Section 9.5), which is a POINTER, ADDRESS, or inplace record followed by a period and the field name. If p points to a the dynamic record described in Figure 10–2, then p.str, p.base, and p.val have the values:
p.str = "hello"
p.base = 53
p.val = 28
10.1. Inplace Records
An inplace record is allocated automatically as a local variable or
as a component of some other object;
this is the same way in which values of simple data types, such as
INTEGER, are allocated.
It is not possible to call the system PROCEDURE new to allocate
an inplace record.
In contrast with a dynamic record, an inplace record is not accessed through a POINTER, but through an inplace record variable. An inplace record variable's type is declared with the keyword $RECORD followed by the name of the record's CLASS in parentheses, as in:
$RECORD(myClass) myRec;
Assignment of one inplace record to another copies each field of the record from the source to the destination.
Two simple record variables (that are not $REFERENCE parameters; see Section 7.5.4) cannot be aliases for each other; each has its own copy of each field of the record, and a change to a field of one of the records is not reflected in a field of another simple inplace record variable. This is also true of simple variables of types like INTEGER; a change to a simple INTEGER variable i does not change the value of another simple INTEGER variable j. Inplace record variables are said to have “value semantics” because their values are independent of one another; this contrasts with the “pointer semantics” of POINTERs used to access dynamic records.
An inplace record is never disposed explicitly, and never garbage
collected;
it goes away automatically when the object of which it is a component
is reclaimed
or when the PROCEDURE in which it is a local variable returns.
10.2. Dynamic Records
A dynamic record is never allocated automatically; it is allocated
by a call to a system PROCEDURE that allocates records.
The system PROCEDURE that allocates records is
usually the system PROCEDURE new, although $newRecords and
$createRecord also allocate records.
Each of these
system PROCEDUREs requires
a CLASS, CLASS descriptor, or POINTER to another record
of the CLASS to be allocated as an
argument, and returns a
POINTER to the newly allocated record(s) of the
specified CLASS.
In contrast with an inplace record, a dynamic record is not accessed through an inplace record variable, but through a POINTER. The CLASS (or a prefix of the CLASS) that a POINTER is allowed to point to is usually included in the POINTER declaration, as in:
POINTER(myClass) myPtr;
Assignment of one POINTER to another makes the destination POINTER point to the same object as the source POINTER.
Two POINTER variables may be aliases for the same dynamic record; that is, they may both point to the same record, and a change to any of the record's fields using one POINTER is reflected when the same field is accessed through the other POINTER. POINTER variables are said to have “pointer semantics”, in contrast to the “value semantics” of inplace record variables.
A dynamic record may be reclaimed by passing a POINTER to it to
the system PROCEDURE
dispose, or it may be reclaimed automatically by the garbage
collector if it becomes inaccessible.
10.3. Inplace Record Declaration
An inplace record r is declared as follows, where
c is a CLASS:
$RECORD(c) r;
The declaration of c must have already been completed; c cannot be a forward (not yet declared) CLASS or a CLASS in the process of being declared. An exception is made if r is a $REFERENCE parameter (see Section 7.5.4), or is a parameter of a FORWARD PROCEDURE p or a PROCEDURE p being declared as a field (of a CLASS or MODULE); in the latter case, c must have been declared by the time the body for p is declared.
It is an error if c is a CLASS that contains PROCEDURE fields.
An inplace record of size 0 (i.e., of a CLASS with no data fields) is not allowed as a local or OWN variable, as a non-$REFERENCE parameter, or as an ARRAY element or PROCEDURE type.
Any variable, parameter, or field can be of an inplace record type.
Any ARRAY may have inplace record elements.
Any PROCEDURE may return an inplace record type.
10.4. Declaration of POINTERs to Dynamic Records
A dynamic record is not declared directly; a POINTER
may be declared to point to a record CLASS, however.
A POINTER p to records of a CLASS c is declared as follows:
POINTER(c) p;
The declaration of c need not have been completed; c can be a forward (not yet declared) CLASS or a CLASS in the process of being declared. However, c must be declared before p is used to access any fields of c.
The prohibition against the use of a CLASS with no data fields that applies to inplace records does not apply to POINTERs or dynamic records; any POINTER may be declared to point to a CLASS with no data fields, and it is legal to allocate a dynamic record of size 0.
Any variable, parameter, or field can be of a POINTER type.
Any ARRAY may have POINTER elements.
Any PROCEDURE may return a POINTER type.
10.5. Inplace Record Allocation
Inplace record allocation occurs implicitly.
If the inplace record is a PROCEDURE local variable
(a PROCEDURE parameter is considered a local variable),
it is allocated automatically on entry to the PROCEDURE.
Otherwise, the inplace record must be a component of some other
object; it is allocated when the enclosing object is allocated.
The contents of an inplace record are cleared (all fields are set to Zero) when an inplace record is allocated, even when the inplace record is a local variable.
As an example, consider the following code:
CLASS c (INTEGER i; STRING s);
CLASS d (LONG REAL rr; $RECORD(c) r);
PROCEDURE proc ($RECORD(c) r1);
BEGIN
$RECORD(c) r2;
POINTER(d) p;
p := new(d);
END;
r1 and r2 are allocated and cleared on entry to proc; i.e., at the start of proc, r1.i and r1.i are zero, and r1.s and r2.s are the empty STRING.
The inplace record field r is a component of
the dynamic record of CLASS d to which
p is made to point by p := new(d).
When the dynamic record is allocated, its field r
is allocated and cleared.
10.6. Dynamic Record Allocation
A dynamic record is most commonly allocated
by calling the system PROCEDURE
new with the CLASS of the record to be allocated as an argument;
see Section 42.1.
A program may call new
any number of times, allocating as many dynamic
records as it needs.
The dynamic records are usually made accessible by setting POINTER
components of other objects to point to the records,
so that the program can keep track of
the dynamic records it allocates.
The PROCEDUREs $createRecord (Section 32.72) and $newRecords (Section 42.6) can also be used to allocate records in the appropriate circumstances, but they are generally not as useful as new.
Newly allocated dynamic records are cleared (all fields are set to Zero).
As an example, consider the following code:
CLASS c (INTEGER i; STRING s);
CLASS(c) d (REAL r);
PROCEDURE proc (POINTER(c) p1);
BEGIN
POINTER(c) p2,p3,p4;
p2 := new(c);
p3 := new(d);
p4 := p1;
END;
On entry to proc, p1 has been initialized from its argument; if the program is correct, then p1 is either NULLPOINTER (pointing at no object) or pointing to an object of CLASS c or of a CLASS derived from c. p2, p3, and p4 are initialized to NULLPOINTER on entry.
The statement p2 := new(c) allocates a dynamic c record, clears it (so that p2.i is zero and p2.s the empty STRING), and sets p2 to point to it.
The statement p3 := new(d) allocates a dynamic d record, clears it (so that p3.i and p3.r are zero and p3.s the empty STRING), and sets p3 to point to it. It is allowed that p3 be declared to point to a c object but actually point to a d object; since d is derived from c, every d object is a c object.
The statement p4 := p1 makes p4 point at the same record as p1 (or sets p4 to NULLPOINTER if p1 is NULLPOINTER). p1 and p4 can then be considered aliases for the same object.
As a more complex example, given the declarations:
the expression
rc.p.r.p would be valid and would refer to the POINTER
field p of the inplace record field
r of the object pointed to by rc's
POINTER field p.
10.7. Inplace Record Operations
Operations on inplace records include field access, assignment,
and parameter passing.
10.7.1. Field Access
A field f of a record r is accessed as r.f;
see Section 9.5.
CLASS c (POINTER(d) p);
CLASS d ($RECORD(c) r);
$RECORD(c) rc;
10.7.2. Assignment
Inplace record assignment copies record fields from one record to
another.
Records can be assigned (with the assignment operator :=) if they are assignment compatible. Two records are assignment compatible if and only if they are of the same CLASS and that CLASS is not unsized (see Section 12.14). For example, let r be a $RECORD(c). r can be assigned or passed to a $RECORD(c); this copies all fields of c.
The assignment can be part of an assignment expression:
$RECORD(c) x,y,z;
z := y := x;
For example, if r1 is a record of d and r2
is a record of c,
and d is a prefix of c, then
r1 := r2:d assigns the values of r2's
d fields to the corresponding fields of
r2, and ignores the fields in r2
that are from c but not from d.
An omitted OPTIONAL USES or MODIFIES
$RECORD(c) parameter is passed a
$RECORD(c) with all fields cleared.
An
omitted OPTIONAL MODIFIES or
PRODUCES record is discarded upon return.
An explicit default value (e.g., using OPTIONAL(v))
is not allowed for record
parameters.
The parameter qualifier $REFERENCE
is allowed for inplace records, although
it is not allowed for simple data types.
A $REFERENCE parameter is effectively an alias for its argument;
see Section 7.5.4.
clear(r), where r is an inplace record, clears all fields of
r.
$expr(inplace record expression) and compiletime calls to
PROCEDUREs with record results are not currently supported.
INIT is not currently allowed for an ARRAY of inplace records.
Currently, inplace records can be passed to or from
a foreign language only
as $REFERENCE parameters (see Section 7.5.4).
10.7.2.1. Explicit Classification and Inplace Records
To
assign or pass r to a $RECORD(d),
where d is a prefix of c, use r:d,
which copies only the fields of r that belong to d.
It is a compiletime error if d is not a prefix of c.
10.7.3. Inplace Records as PROCEDURE Parameters
As with other types of PROCEDURE arguments and parameters,
a USES inplace record argument
effectively assigns its value to the corresponding parameter,
a PRODUCES inplace record parameter effectively assigns its value
(at the time the PROCEDURE returns) to the corresponding argument,
and a MODIFIES argument/parameter pair assigns in both directions.
Assignment of inplace records means copying fields, as described
above.
10.7.4. Miscellaneous Operations
An IF expression can have a record result:
CLASS c (INTEGER i,j);
$RECORD(c) x,y,z;
. . .
write(logFile,(IF procTrue THEN z EL x).i,eol,
(IF procFalse THEN y EL z).j,eol);
10.7.4.1. Inplace Records and the Foreign Language Interface
10.8. Dynamic Record Operations
10.8.1. Field Access
A field f of a dynamic record referenced by a POINTER p
is accessed as p.f;
see Section 9.5.
As a more complex example, given the declarations:
CLASS c (POINTER(d) p);
CLASS d ($RECORD(c) r);
POINTER(c) pc;
the expression pc.p.r.p would be valid and would refer to the POINTER field p of the inplace record field r of the object pointed to by the POINTER field p of the object pointed to by pc.
Section 9.6 describes explicit classification in detail.
10.8.1.1. Explicit Classification and Dynamic Record Fields
A POINTER p can be temporarily treated as a
POINTER to a CLASS c
different from the CLASS (if any) to which
p was declared to point,
using the syntax p:c.
Such an explicitly classified
POINTER can be used to access a field f
of c (assuming that
CLASS of the object p points to really is c or
a CLASS derived from c) with the syntax p:c.f.
10.8.2. Assignment
POINTER assignment does not copy record fields; it just makes the
destination POINTER point to the same object as the source
POINTER.
The declared CLASSes of the
POINTERs must be assignment compatible,
as described in Section 4.9.
After a (non-NULLPOINTER) POINTER assignment, the two POINTERs in question are effectively aliases of the same object. For example, given:
p.i := 3; q := p; q.i .+ 1;
p.i is 4.
A POINTER assignment can be part of an assignment expression:
POINTER(c) x,y,z;
z := y := x;
10.8.2.1. Copying Fields from One Dynamic Record to Another
POINTER assignment does not copy record fields.
To get an effect with dynamic records similar to that of assigning
inplace records to one another, use $ref (see Section 7.5.4.5):
POINTER(c) p,q;
# Assume q is not NULLPOINTER:
p := q; # p and q point at the same object
# now
p := new(c);
$ref(p) := $ref(q); # p and q point at different
# objects now, but p's c fields
# are a copy of q's
The use of dispose is therefore inherently unsafe, although judicious use of dispose can improve performance in many programs by reducing garbage collection time. A facility called POINTER rigging (see Chapter 6 of the MAINDEBUG User's Guide) can sometimes make it easier to track dangling POINTER bugs. SUPERCHECK platforms (see Chapter 18) may also catch dangling POINTER problems.
It is never necessary to dispose records explicitly, since a
record that becomes inaccessible
(no longer pointed to by any accessible POINTER)
is automatically collected by the
MAINSAIL garbage collector;
the only reason to call dispose is to improve program performance.
An inplace $RECORD(complex) represents a single complex value, and
assignment and parameter passing assign that value to another variable
without accidentally introducing aliases to the same object.
Dynamic records are useful when the number of records in a data
structure is not known beforehand, as in a list of arbitrary size,
or where all uses of a particular
record value must be through POINTERs to the same record, as
in MAINSAIL file records (because a duplicate of a file record is not
valid).
The bulk of the data allocated by
most programs consists of dynamic objects,
because the ability to link dynamic objects through POINTERs
to produce data structures of arbitrary topology allows great
flexibility.
If you want to create a PROCEDURE to which you can pass either an
inplace record of a CLASS c or a POINTER to c, declare the
parameter in question as a $REFERENCE $RECORD(c).
Pass an argument declared as POINTER(c) p to this parameter as
$ref(p).
See Section 7.5.4 for more details on
$REFERENCE parameters
and $ref.
10.11. When to Use Inplace Records and When to Use Dynamic Records
Inplace objects are most useful for records of relatively small
CLASSes that represent a particular value,
where a copy of the value is as valid as the original value.
A classic example of inplace record usage would be for complex
numbers, e.g.:
CLASS complex (LONG REAL realPart,imaginaryPart);
10.12. How to Make a PROCEDURE Able to Take Both Inplace Records
and POINTERs to Dynamic Records as Arguments
Sometimes it is useful to use the same CLASS to classify both
inplace and dynamic records.
10.13. The Layout of Fields within a Record
The order of the fields of a MAINSAIL record is subject to change, and code that depends upon this order should be avoided. Generally speaking, it is not possible to write code depending on the layout of a MAINSAIL CLASS without using low-level ADDRESS arithmetic or PROCEDUREs that inquire at runtime about the fields of a CLASS.
CLASSes representing foreign language data structures to be passed through the Foreign Language Interface should be declared with the $ALIGN directive. Such CLASSes are guaranteed to be laid out as the foreign language expects, not as described below.
At present, consecutively declared fields of an unaligned CLASS are stored at increasing memory locations within each record of that CLASS. On all existing 32-bit platforms, each field occupies exactly the number of storage units given by size(typeCode), where typeCode is the INTEGER type code for the field's data type; no padding or packing is done. For example, if a CLASS is declared as:
CLASS xyz (
INTEGER i;
POINTER(xyz) p;
LONG INTEGER li;
);
and, on the (32-bit) machine where the record is stored:
size(integerCode) = 2
size(pointerCode) = 4
size(longIntegerCode) = 4
then:
On all existing 64-bit platforms, padding is sometimes inserted between fields, as required by the platform's alignment rule; see Table 9–3. For example, on HPPA64, the above record would be laid out so that:
10.13.1. The Layout of Fields within a Record of a Prefixed CLASS
At present, fields contributed directly by a prefixed CLASS
immediately follow those fields contributed by the prefix CLASS
in memory.
There is no padding (except that required by the platform's alignment
rule, as listed in Table 9–3)
or extra fields between fields of a prefix
CLASS and those of its prefixed
CLASS in the prefixed CLASS record.
For example, given the declarations:
CLASS c1 (INTEGER i; STRING s);
CLASS(c1) c2 (BITS b; REAL r);
a record of c2 contains the fields i, s, b, and r at progressively higher addresses in memory.
MAINSAIL Language Manual, Chapter 10