This section documents a GNU Modula-2 compiler switch which implements
a language optimization surrounding the implementation of unbounded
arrays. In GNU Modula-2 the unbounded array is implemented by
utilizing an internal structure struct {dataType *address,
unsigned int high}
. So given the Modula-2 procedure declaration:
PROCEDURE foo (VAR a: ARRAY OF dataType) ; BEGIN IF a[2]= (* etc *) END foo ;
it is translated into GCC tree
s, which can be represented
in their C form thus:
void foo (struct {dataType *address, unsigned int high} a) { if (a.address[2] == /* etc */ }
Whereas if the procedure foo
was declared as:
PROCEDURE foo (a: ARRAY OF dataType) ; BEGIN IF a[2]= (* etc *) END foo ;
then it is implemented by being translated into the following
GCC tree
s, which can be represented in their C form thus:
void foo (struct {dataType *address, unsigned int high} a) { dataType *copyContents = (dataType *)alloca (a.high+1); memcpy(copyContents, a.address, a.high+1); a.address = copyContents; if (a.address[2] == /* etc */ }
This implementation works, but it makes a copy of each non VAR
unbounded array when a procedure is entered. If the unbounded array
is not changed during procedure foo
then this implementation
will be very inefficient. In effect Modula-2 lacks the REF
keyword of Ada. Consequently the programmer maybe tempted to
sacrifice semantic clarity for greater efficiency by declaring the
parameter using the VAR
keyword in place of REF
.
The -funbounded-by-reference
switch instructs the compiler to
check and see if the programmer is modifying the content of any
unbounded array. If it is modified then a copy will be made upon
entry into the procedure. Conversely if the content is only read and
never modified then this non VAR
unbounded array is a candidate
for being passed by reference. It is only a candidate as it is still
possible that passing this parameter by reference could alter the
meaning of the source code. For example consider the following case:
PROCEDURE StrConCat (VAR a: ARRAY OF CHAR; b, c: ARRAY OF CHAR) ; BEGIN (* code which performs string a := b + c *) END StrConCat ; PROCEDURE foo ; VAR a: ARRAY [0..3] OF CHAR ; BEGIN a := 'q' ; StrConCat(a, a, a) END foo ;
In the code above we see that the same parameter, a
, is being
passed three times to StrConCat
. Clearly even though parameters
b
and c
are never modified it would be incorrect to
implement them as pass by reference. Therefore the compiler checks to
see if any non VAR
parameter is type compatible with any
VAR
parameter and if so it generates run time procedure entry
checks to determine whether the contents of parameters b
or
c
matches the contents of a
. If a match is detected
then a copy is made and the address
in the unbounded
struct
ure is modified.
The compiler will check the address range of each candidate against
the address range of any VAR
parameter, providing they are type
compatible. For example consider:
PROCEDURE foo (a: ARRAY OF BYTE; VAR f: REAL) ; BEGIN f := 3.14 ; IF a[0]=BYTE(0) THEN (* etc *) END END foo ; PROCEDURE bar ; BEGIN r := 2.0 ; foo(r, r) END bar ;
Here we see that although parameter, a
, is a candidate for the
passing by reference, it would be incorrect to use this
transformation. Thus the compiler detects that parameters, a
and f
are type compatible and will produce run time checking
code to test whether the address range of their respective contents
intersect.