comp.lang.ada
 help / color / mirror / Atom feed
* Memory management clarification
@ 2005-07-26  9:57 Maciej Sobczak
  2005-07-26 10:38 ` Adrien Plisson
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Maciej Sobczak @ 2005-07-26  9:57 UTC (permalink / raw)


Hi,

Trying to learn a bit of Ada I came across a statement that memory 
allocated from the pool will be implicitly reclaimed when the acces 
variable used to reference it goes out of scope.
That's nice, but I would like to learn a bit more about the exact 
mechanics of this and about the guarantees that it can provide.

Let's say that there is some MyType definition and this:

type MyTypeRef is access MyType;

1.

declare
     X : MyTypeRef;
begin
    loop
       X := new MyType;
    end loop;  -- infinite loop just for the sake of discussion
end;

Note that X does not goes out of scope when the loop is executing.
Will the memory be reclaimed? When? What can be said about the memory 
consumption of such program? Is it bounded and guaranteed? Is it 
necessarily larger than without the loop (just single allocation)?

2.

loop
    declare
       X : MyTypeRef;
    begin
       X := new MyType;
    end;
end loop;

What now? Is this any different from the memory management point of view?

3.

declare
    X : MyTypeRef;
begin
    X := new MyType;
    X := new MyType;
    X := new MyType;
    X := new MyType;
    -- ...
end;

When is the memory reclaimed for each allocated object? At each 
subsequent assignment? Or maybe at the end of the block? Or even 
"sometime later"? Or maybe all subsequent assignments are eliminated by 
compiler?

4.

Is it possible to associate some function with object allocated by new, 
which would be called at the time (or maybe after) the object is reclaimed?
Yes, I'm asking about destructors or finalizers.

5.

Is it possible to "overload" new for MyType so that the X := new MyType; 
statement will do whatever *I* want it to do, including actual memory 
allocation? If yes, is it possible to hook on memory reclamation as well?

6.

What about reference cycles between dynamically allocated objects?

declare
    type ListNode;
    type ListNodeRef is access ListNode;
    type ListNode is record
       SomeData : Integer;
       Other : ListNodeRef;
    end record;
    First, Second : ListNodeRef;
begin
    First := new ListNode;
    First.all.SomeData := 7;
    Second := new ListNode;
    Second.all.SomeData := 8;

    First.all.Other := Second;
    Second.all.Other := First; -- cycle
end;

Will the memory be reclaimed?


Regards,


-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26  9:57 Memory management clarification Maciej Sobczak
@ 2005-07-26 10:38 ` Adrien Plisson
  2005-07-26 14:19   ` Robert A Duff
  2005-07-26 13:57 ` Frank J. Lhota
  2005-07-26 14:17 ` Robert A Duff
  2 siblings, 1 reply; 9+ messages in thread
From: Adrien Plisson @ 2005-07-26 10:38 UTC (permalink / raw)


Maciej Sobczak wrote:
> Trying to learn a bit of Ada I came across a statement that memory 
> allocated from the pool will be implicitly reclaimed when the acces 
> variable used to reference it goes out of scope.

this is called garbage collection. can you tell us where you read this 
statement ?

if i remember right, garbage collection is _allowed_ by the standard, 
but not _defined_ in the same standard.

the only 2 compilers i can think of implementing a GC are JGNAT and 
MGNAT, 2 niche compilers targeting the JVM and CLI, mainly because the 
GC is a feature of their target platform. i'm not aware of any other 
implementation of a garbage collector, but i may be wrong...

-- 
rien



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26  9:57 Memory management clarification Maciej Sobczak
  2005-07-26 10:38 ` Adrien Plisson
@ 2005-07-26 13:57 ` Frank J. Lhota
  2005-07-26 14:21   ` Robert A Duff
  2005-07-26 14:17 ` Robert A Duff
  2 siblings, 1 reply; 9+ messages in thread
From: Frank J. Lhota @ 2005-07-26 13:57 UTC (permalink / raw)


Maciej Sobczak wrote:
> Hi,
> 
> Trying to learn a bit of Ada I came across a statement that memory 
> allocated from the pool will be implicitly reclaimed when the acces 
> variable used to reference it goes out of scope.

No, that is not true. What is true is that if an access type goes out of 
scope, then the data allocated from the pool for that type will be 
reclaimed. See ARM 13.11 (18).

Most Ada implementations do not support garbage collection, and with 
such implementations, your examples would simply create a lot of garbage.



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26  9:57 Memory management clarification Maciej Sobczak
  2005-07-26 10:38 ` Adrien Plisson
  2005-07-26 13:57 ` Frank J. Lhota
@ 2005-07-26 14:17 ` Robert A Duff
  2005-07-26 15:39   ` Maciej Sobczak
  2 siblings, 1 reply; 9+ messages in thread
From: Robert A Duff @ 2005-07-26 14:17 UTC (permalink / raw)


Maciej Sobczak <no.spam@no.spam.com> writes:

> Hi,
> 
> Trying to learn a bit of Ada I came across a statement that memory
> allocated from the pool will be implicitly reclaimed when the acces
> variable used to reference it goes out of scope.

If you use the default storage pool(s), the language standard does not
specify when heap memory is reclaimed.  Most implementations do not
reclaim any such memory unless you explicitly call
Unchecked_Deallocation.

If you put "for T'Storage_Size use 1_000_000;", then implementations
should reclaim all memory allocated for type T when the scope of T is
left.  The *type* -- not individual variables of the type.  This is not
a very useful capability, because most access types need to be at
library level, so the memory won't be reclaimed until the whole program
is done.

You should look up user defined storage pools.  You can say:

    for T'Storage_Pool use My_Pool;

and then you can control when memory will be reclaimed.
You can reclaim all memory in My_Pool whenever you like -- but
beware dangling pointers.

> That's nice, but I would like to learn a bit more about the exact
> mechanics of this and about the guarantees that it can provide.
> 
> Let's say that there is some MyType definition and this:
> 
> type MyTypeRef is access MyType;
> 
> 1.
> 
> declare
>      X : MyTypeRef;
> begin
>     loop
>        X := new MyType;
>     end loop;  -- infinite loop just for the sake of discussion
> end;
> 
> Note that X does not goes out of scope when the loop is executing.
> Will the memory be reclaimed?

No.

>... When? What can be said about the memory
> consumption of such program? Is it bounded and guaranteed?

No.  Unless the implementation supports garbage collection,
you will eventually run out of memory.  Most implementations
do not support GC.

>... Is it
> necessarily larger than without the loop (just single allocation)?
> 
> 2.
> 
> loop
>     declare
>        X : MyTypeRef;
>     begin
>        X := new MyType;
>     end;
> end loop;
> 
> What now? Is this any different from the memory management point of view?

No.  X.all will never be reclaimed (on most implementations).  The only
difference here is that you're allocating only one object.

Suppose we added a call P(X) inside the begin/end.  And suppose P saves
X in a global variable.  The implementation cannot deallocate the memory
X points to, because that would leave a dangling pointer in the global
variable.  A garbage collector's job is to tell whether or not that
happened -- then it can reclaim all memory not reachable.  But most Ada
implementations don't do GC -- they just assume X.all *might* be
reachable, and never reclaim it unless you do U_D.

> 3.
> 
> declare
>     X : MyTypeRef;
> begin
>     X := new MyType;
>     X := new MyType;
>     X := new MyType;
>     X := new MyType;
>     -- ...
> end;
> 
> When is the memory reclaimed for each allocated object? At each
> subsequent assignment? Or maybe at the end of the block? Or even
> "sometime later"? Or maybe all subsequent assignments are eliminated by
> compiler?

Never.  (Well, "never" really means "when the process exits".)

> 4.
> 
> Is it possible to associate some function with object allocated by new,
> which would be called at the time (or maybe after) the object is
> reclaimed?
> Yes, I'm asking about destructors or finalizers.

Yes.  Look up "controlled types".  These allow you to associate a
Finalize operation with a type.  For local variables, Finalize will be
automatically called when the procedure is left.  For heap objects,
Finalize will be called when you do Unchecked_Deallocation (or, if you
never do U_D, when the program is done).

> 5.
> 
> Is it possible to "overload" new for MyType so that the X := new MyType;
> statement will do whatever *I* want it to do, including actual memory
> allocation? If yes, is it possible to hook on memory reclamation as well?

"new" does two things: allocate memory, and initialize it (explicitly or
implicltly).  You can't exactly "overload new", but you can use storage
pools as described above -- this gives you control over the memory
allocation part of "new".  The initialization still happens in the
normal way.

> 6.
> 
> What about reference cycles between dynamically allocated objects?
> 
> declare
>     type ListNode;
>     type ListNodeRef is access ListNode;
>     type ListNode is record
>        SomeData : Integer;
>        Other : ListNodeRef;
>     end record;
>     First, Second : ListNodeRef;
> begin
>     First := new ListNode;
>     First.all.SomeData := 7;
>     Second := new ListNode;
>     Second.all.SomeData := 8;
> 
>     First.all.Other := Second;
>     Second.all.Other := First; -- cycle
> end;
> 
> Will the memory be reclaimed?

Cycles don't make any difference.

- Bob



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26 10:38 ` Adrien Plisson
@ 2005-07-26 14:19   ` Robert A Duff
  0 siblings, 0 replies; 9+ messages in thread
From: Robert A Duff @ 2005-07-26 14:19 UTC (permalink / raw)


Adrien Plisson <aplisson-news@stochastique.net> writes:

> the only 2 compilers i can think of implementing a GC are JGNAT and
> MGNAT, 2 niche compilers targeting the JVM and CLI, mainly because the
> GC is a feature of their target platform. i'm not aware of any other
> implementation of a garbage collector, but i may be wrong...

SofCheck also has a JVM-targetted compiler, and it does GC.
A# targets the .NET platform, so I assume it does GC also.

- Bob



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26 13:57 ` Frank J. Lhota
@ 2005-07-26 14:21   ` Robert A Duff
  2005-07-26 18:11     ` Frank J. Lhota
  0 siblings, 1 reply; 9+ messages in thread
From: Robert A Duff @ 2005-07-26 14:21 UTC (permalink / raw)


"Frank J. Lhota" <NOSPAM.lhota@adarose.com> writes:

> Maciej Sobczak wrote:
> > Hi,
> > Trying to learn a bit of Ada I came across a statement that memory
> > allocated from the pool will be implicitly reclaimed when the acces
> > variable used to reference it goes out of scope.
> 
> No, that is not true. What is true is that if an access type goes out of
> scope, then the data allocated from the pool for that type will be
> reclaimed. See ARM 13.11 (18).

That's not quite true, either.  Most implementations use a global heap
by default, and never automatically reclaim memory.  If you use a
Storage_Size clause on a local access type, then what you say is true.
But local access types are not very useful.

> Most Ada implementations do not support garbage collection, and with
> such implementations, your examples would simply create a lot of garbage.

Right.

- Bob



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26 14:17 ` Robert A Duff
@ 2005-07-26 15:39   ` Maciej Sobczak
  2005-07-26 17:45     ` Robert A Duff
  0 siblings, 1 reply; 9+ messages in thread
From: Maciej Sobczak @ 2005-07-26 15:39 UTC (permalink / raw)


Robert A Duff wrote:

> If you use the default storage pool(s), the language standard does not
> specify when heap memory is reclaimed.  Most implementations do not
> reclaim any such memory unless you explicitly call
> Unchecked_Deallocation.

OK.

> If you put "for T'Storage_Size use 1_000_000;", then implementations
> should reclaim all memory allocated for type T when the scope of T is
> left.

Nice. Why putting this arbitrary limit?

And what does it mean, anyway - is the pool actually pre-allocated with 
this size when the for..use... statement is executed, or does it start 
empty and later "inflates" as necessary, but no bigger than the given limit?
The difference is not only the observable memory consumption (even when 
not the whole pool is used), but also the possibility and timing of 
low-memory errors.

> This is not
> a very useful capability, because most access types need to be at
> library level, so the memory won't be reclaimed until the whole program
> is done.

Indeed, not really useful.

> You should look up user defined storage pools.  You can say:
> 
>     for T'Storage_Pool use My_Pool;
> 
> and then you can control when memory will be reclaimed.
> You can reclaim all memory in My_Pool whenever you like -- but
> beware dangling pointers.

I have to beware them when using Unchecked_Deallocation as well. :)


>>2.
>>
>>loop
>>    declare
>>       X : MyTypeRef;
>>    begin
>>       X := new MyType;
>>    end;
>>end loop;
>>
>>What now? Is this any different from the memory management point of view?
> 
> No.  X.all will never be reclaimed (on most implementations).  The only
> difference here is that you're allocating only one object.

Not really - there's a loop.
I understand that the code above leaks memory, just like my first example.


> Suppose we added a call P(X) inside the begin/end.  And suppose P saves
> X in a global variable.  The implementation cannot deallocate the memory
> X points to, because that would leave a dangling pointer in the global
> variable.

Right, but I was interested exactly in the case where there is just one 
reference and it is trivial for the compiler to prove that the object is 
not aliased. But without GC there's no difference anyway.


>>4.
>>
>>Is it possible to associate some function with object allocated by new,
>>which would be called at the time (or maybe after) the object is
>>reclaimed?
>>Yes, I'm asking about destructors or finalizers.
> 
> Yes.  Look up "controlled types".  These allow you to associate a
> Finalize operation with a type.  For local variables, Finalize will be
> automatically called when the procedure is left.

Good - this, basically, should allow me to implement some form of local 
resource manager (in C++ this idiom is called RAII) that will deallocate 
the object (or any other resource) when the scope is left.

> For heap objects,
> Finalize will be called when you do Unchecked_Deallocation (or, if you
> never do U_D, when the program is done).

Fine. Is the order of calling finalizers well-defined for the latter case?


Thank you for these explanations,

-- 
Maciej Sobczak : http://www.msobczak.com/
Programming    : http://www.msobczak.com/prog/



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26 15:39   ` Maciej Sobczak
@ 2005-07-26 17:45     ` Robert A Duff
  0 siblings, 0 replies; 9+ messages in thread
From: Robert A Duff @ 2005-07-26 17:45 UTC (permalink / raw)


Maciej Sobczak <no.spam@no.spam.com> writes:

> Robert A Duff wrote:
> > If you put "for T'Storage_Size use 1_000_000;", then implementations
> > should reclaim all memory allocated for type T when the scope of T is
> > left.
> 
> Nice. Why putting this arbitrary limit?

You can put any limit you like.  It does not need to be known at compile
time.

Note that this feature is inherited from Ada 83.  It is subsumed by
storage pools (added in Ada 95).

> And what does it mean, anyway - is the pool actually pre-allocated with
> this size when the for..use... statement is executed, or does it start
> empty and later "inflates" as necessary, but no bigger than the given
> limit?
> The difference is not only the observable memory consumption (even when
> not the whole pool is used), but also the possibility and timing of
> low-memory errors.

It means approximately 1_000_000 storage units are *reserved* for this
type.  You are guaranteed to be able to allocate (approx) that much.
One possible implementation is to allocate that space on the stack of
the current procedure, and allocate the heap objects within that.
Then it gets automatically freed when the procedure returns.

But the implementation is not required to consume the memory -- for
example, it can allocate *virtual* address space, but not physical
memory or swap space.

The timing of out-of-memory errors (Storage_Error exception) is a
problem anyway -- according to the standard, you can get Storage_Error
at any time.

If you don't like the "reserve arbitrary limit" semantics, you can write
your own storage pool type that has whatever semantics you like.  For
example, reserve nothing, but still deallocate on procedure return.
The Finalize of the storage pool type comes in handy for that.

> > This is not
> > a very useful capability, because most access types need to be at
> > library level, so the memory won't be reclaimed until the whole program
> > is done.
> 
> Indeed, not really useful.
> 
> > You should look up user defined storage pools.  You can say:
> >     for T'Storage_Pool use My_Pool;
> > and then you can control when memory will be reclaimed.
> > You can reclaim all memory in My_Pool whenever you like -- but
> > beware dangling pointers.
> 
> I have to beware them when using Unchecked_Deallocation as well. :)

Indeed.  ;-)

> >>2.
> >>
> >>loop
> >>    declare
> >>       X : MyTypeRef;
> >>    begin
> >>       X := new MyType;
> >>    end;
> >>end loop;
> >>
> >>What now? Is this any different from the memory management point of view?
> > No.  X.all will never be reclaimed (on most implementations).  The only
> > difference here is that you're allocating only one object.
> 
> Not really - there's a loop.
> I understand that the code above leaks memory, just like my first example.

Yes, both leak memory.  The loop probably leaks faster.  ;-)

> > Suppose we added a call P(X) inside the begin/end.  And suppose P saves
> > X in a global variable.  The implementation cannot deallocate the memory
> > X points to, because that would leave a dangling pointer in the global
> > variable.
> 
> Right, but I was interested exactly in the case where there is just one
> reference and it is trivial for the compiler to prove that the object is
> not aliased.

I don't know of any compiler that does this trivial proof.
It's such a special case that it wouldn't be all that useful anyway.

>... But without GC there's no difference anyway.
> 
> 
> >>4.
> >>
> >>Is it possible to associate some function with object allocated by new,
> >>which would be called at the time (or maybe after) the object is
> >>reclaimed?
> >>Yes, I'm asking about destructors or finalizers.
> > Yes.  Look up "controlled types".  These allow you to associate a
> > Finalize operation with a type.  For local variables, Finalize will be
> > automatically called when the procedure is left.
> 
> Good - this, basically, should allow me to implement some form of local
> resource manager (in C++ this idiom is called RAII) that will deallocate
> the object (or any other resource) when the scope is left.

Right -- it's pretty much the same as in C++.

> > For heap objects,
> > Finalize will be called when you do Unchecked_Deallocation (or, if you
> > never do U_D, when the program is done).
> 
> Fine. Is the order of calling finalizers well-defined for the latter case?

I don't remember the exact rules.  The order is somewhat left up to the
implementation, but you'd have to read the RM to understand the details.

> Thank you for these explanations,

You're welcome.

- Bob



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Memory management clarification
  2005-07-26 14:21   ` Robert A Duff
@ 2005-07-26 18:11     ` Frank J. Lhota
  0 siblings, 0 replies; 9+ messages in thread
From: Frank J. Lhota @ 2005-07-26 18:11 UTC (permalink / raw)


Robert A Duff wrote:
> "Frank J. Lhota" <NOSPAM.lhota@adarose.com> writes:
> That's not quite true, either.  Most implementations use a global heap
> by default, and never automatically reclaim memory.  If you use a
> Storage_Size clause on a local access type, then what you say is true.
> But local access types are not very useful.

You're right, I left out the Storage_Size condition.

Although local access types are very rare, I have seen code where a 
local access type is used to create a temporary structure that is used 
for the subprogram call, then conveniently goes away when the subprogram 
completes. So I wouldn't say that local access types are not very 
useful, just not very common.



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2005-07-26 18:11 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-07-26  9:57 Memory management clarification Maciej Sobczak
2005-07-26 10:38 ` Adrien Plisson
2005-07-26 14:19   ` Robert A Duff
2005-07-26 13:57 ` Frank J. Lhota
2005-07-26 14:21   ` Robert A Duff
2005-07-26 18:11     ` Frank J. Lhota
2005-07-26 14:17 ` Robert A Duff
2005-07-26 15:39   ` Maciej Sobczak
2005-07-26 17:45     ` Robert A Duff

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox