TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy

TEAM-ADA@LISTSERV.ACM.ORG

Options: Use Classic View

Use Monospaced Font
Show Text Part by Default
Show All Mail Headers

Topic: [<< First] [< Prev] [Next >] [Last >>]

Print Reply
Stanley Allen <[log in to unmask]>
Mon, 11 May 1998 03:09:10 -0500
text/plain (258 lines)
[log in to unmask] wrote:
>
> Stanley Allen wrote:
> >Finalize is called not only at the point of object
> >destruction but also at the point of assignment, when the
> >value is being overwritten.
>
>    Consider X := Y;
> Just before its execution there are two separate and perhaps
> differing object X and Y.  The old X must first be destroyed
> since it is going to go out of existence.  For instance, if X
> is on a linked list, it will need to be unlinked.  So
> Finalize is called.  Then the area of memory formerly
> occupied by X can be used for a copy of the bits of Y.  At
> this point there is only one object, Y, in existence, though
> the same bit pattern may be present at X'address.  But if,
> say, Y was on a linked list, that list will only have Y, not
> the copy.  Then Adjust is called to turn the bit pattern at
> X'address into a new object X.  Continuing the linked list
> example, Adjust would splice the new X into the list.
> (Ignoring the stuff about a temp to allow X := X; to work.)

Ok, this helps some more; I may need to chew
more on it before trying to swallow, however.

>
> >which may cause Finalize to be called twice --
> The RM explicitly mentions that Finalize may be called
> multiple times, so the simple reference counting in the
> example won't do.  But you can add
>   Has_Been_Finalized : Boolean := False;
> to the record definition and start Finalize with
>   if Obj.Has_Been_Finalized then return;
>   else Obj.Has_Been_Finalized := True;end if;

Excellent suggestion.

>
> >Example #1, assume the spec of Final is as given
> >...
> >end
> >.-1
> >...
> >But does this make sense?  Null_Finis was never
> >Initialized or Adjusted.  Now my ref counts are
> >messed up at the end.
>   I agree.  It seems it should have been.  And
> removing 'constant', or moving from private
> spec to body, or adding an 'aliased' component,
> none seem to have any affect.

This example gets more confusing to me the
more I look at it.  For convenience, let me
repeat the spec:

-------------------------------
with Ada.Finalization; use Ada.Finalization;
package Final is

    type Finis is private;
    function Get_Count return Integer;
    function Create return Finis;

private

    type Finis is new Controlled with null record;

    procedure Initialize (Obj : in out Finis);
    procedure Adjust (Obj : in out Finis);
    procedure Finalize (Obj : in out Finis);

    Null_Finis : constant Finis := (Controlled with null record);

end Final;
--------------------------------

At the point of declaration of Null_Finis,
the specs of the Init/Adj/Fin procedures for
type Finis have been declared, but the bodies
have not.  (BTW, the compilers complain that
the declaration of the constant Null_Finis
above these spec declarations is illegal because
Null_Finis 'freezes' the type.)  This declaration
should cause Adjust to be called for Null_Finis;
however, Adjust's body has not yet been declared.
I would normally interpret this as cause for
raising a Program_Error, the failure occuring at
an elaboration check.  Or... is it that the body
of Adjust inherited from Controlled is being called?

If this is true, it's counter-intuitive, and something
of a gotcha.  It means that the package body must
explicitly do the work of making "Adjust" happen for
any constant objects of types derived from Controlled,
which must be done in some other way than directly
calling Adjust for the constant object, because it's
a constant and Adjust can only accept a variable as
an argument!

This would be even worse when we have constants of
types further down in the heirarchy: a deferred
constant of type Y indirectly decended from
Controlled through X would always be adjusted using
X's Adjust, which may have the wrong meaning for an
object of type Y.

Unfortunately, I cannot determine what the effect
is of declaring such constants because when I feed
the following code to my two compilers at home (GNAT
and Aonix), the results diverge:

----------------------------------------
with Ada.Finalization; use Ada.Finalization;
package Xxx is
    type X is tagged private;
private

    type X is new Controlled with record
        A : Integer;
        B : Float;
    end record;

    procedure Initialize (Obj : in out X);
    procedure Adjust (Obj : in out X);
    procedure Finalize (Obj : in out X);

end Xxx;
------------------------------------------
with Text_Io; use Text_Io;
package body Xxx is

    procedure Initialize (Obj : in out X) is begin
        Put_Line ("Initialize called for object of type X"); end;
    procedure Adjust (Obj : in out X) is begin
        Put_Line ("Adjust called for object of type X"); end;
    procedure Finalize (Obj : in out X) is begin
        Put_Line ("Finalize called for object of type X"); end;

end Xxx;
-------------------------------------------
with Xxx; use Xxx;
package Yyy is
    type Y is tagged private;
    Null_Y : constant Y;        -- common, innocuous
private

    type Y is new X with null record;

    procedure Initialize (Obj : in out Y);
    procedure Adjust (Obj : in out Y);
    procedure Finalize (Obj : in out Y);

    -- the big question: what's happening here?
    Null_Y : constant Y := (X with null record);

end Yyy;
--------------------------------------------
with Text_Io; use Text_Io;
package body Yyy is

    procedure Initialize (Obj : in out Y) is begin
        Put_Line ("Initialize called for object of type Y"); end;
    procedure Adjust (Obj : in out Y) is begin
        Put_Line ("Adjust called for object of type Y"); end;
    procedure Finalize (Obj : in out Y) is begin
        Put_Line ("Finalize called for object of type Y"); end;

end Yyy;
---------------------------------------------
with Yyy; use Yyy;
with Text_Io; use Text_Io;
procedure Usey is            -- main program
    Y1 : Y;
begin
    Y1 := Null_Y;
end Usey;
----------------------------------------------

Aonix (Win95 7.1) says this when I run Usey:

Initialize called for object of type X
Initialize called for object of type X
Finalize called for object of type X
Adjust called for object of type X
Finalize called for object of type X
Finalize called for object of type X

which can't be right.

GNAT (Win95 3.10p) says this:

Initialize called for object of type Y
Finalize called for object of type Y
Adjust called for object of type Y
Finalize called for object of type Y
Finalize called for object of type Y

Wierd, huh?  For GNAT, there are no objects
of type X; for Aonix, no objects of type Y.
In GNAT's scheme, there is an "extra" Finalize
which matches no Initialize or Adjust.  For
Aonix, they match up, but it's obviously bogus.

> [for example 2]
>
>   If it had Initialized/Adjusted the (Controlled with ...)
> object, or failed to Finalize it, then all would be well.
> It seems clear that I/A shouldn't be called on a constant
> aggregate (see RM 7.6(11)), but one would think neither
> should Finalize.  What happens if Finalize modifies (eg,
> a Has_Been_Finalied flag) in the constant aggregate?

I'll check, hold on a second.

Ok, it's no dice: the same results as before,
with both compilers.

It's not clear to me that RM 7.6(11) that the aggregate
as a whole does not cause Init/Adj to be called; that
sentence seems to be discussing what happens to the
contents of the aggregate rather than the temporary
object being creating by the aggregate itself.
However, the RM is often opaque to me and I won't
assume this paragraph doesn't mean what you think
it does.

For fast relief I run to Cohen's book (the common
man's substitute for RM95!), which says this on
page 572: "...if an object to which a value
has been assigned is itself controlled, Adjust is
called for that object.  An object is considered
to have a value assigned to it in the following
circumstances: ... (*) Evaluation of an aggregate
results in the creation of a new object.  The
component values specified in the aggregate are
assigned to the components of this object.
(*) Except in the case of a function result
returned by reference ... the execution of a
return statement in a function body creates
a new object to hold the function result.  The
value of the expression in the return statement
is assigned to this object."

To me this means that a statement like this in
a function body:

    return (Controlled with null record);

creates two objects, and both of them should
have Adjust called.  I expect both of these
temporary objects to be subject to finalization
also.

Somebody's wrong somewhere.

Stanley Allen
mailto:[log in to unmask]

ATOM RSS1 RSS2