[Nickle] Nickle storage lifetime modifiers
Barton C Massey
bart at cs.pdx.edu
Sun Nov 7 00:44:13 PST 2004
One valid way to think of global, static, and auto lifetimes
is this:
+ Storage with auto lifetime is allocated every time
an auto definition is encountered.
+ Storage with global lifetime is allocated
exactly once, when the function defining it is first
encountered.
+ Storage with static lifetime is the tricky case.
Static storage is allocated whenever the function
containing it is defined, and reallocated whenever
the definition is re-evaluated.
Note that because C doesn't have nested functions and
closures, global and static would both behave the same
there. This is why C has only the static keyword :-).
We say that Nickle has separate notions of the scope of a
name and the lifetime of storage: a name may go out of scope
without its storage being deallocated, and may come into
scope and be bound to previously allocated storage.
Consider these functions, similar to yours:
int() cntfac()
/* counter factory */
{
return (int func() {
return (static int x = 0)++;
});
}
int() cntfac2()
/* counter factory */
{
int bar() {
return (static int x = 0)++;
}
return bar;
}
These functions behave identically, as you would hope. The
inner function definitions in both are evaluated every time
the outer function is invoked. Thus, new storage is
allocated for x at that point, and returned. Now consider
what happens if we say:
int() cntfac3()
/* shared counter factory */
{
static int bar() {
return (static int x = 0)++;
}
return bar;
}
Because bar() itself is static, its definition is only
evaluated once, at the time the definition of cntfac3() is
evaluated. Thus the definition of x is only evaluated
once, and all the "shared counters" returned by cntfac3() work
off the same storage for x.
Finally, consider more or less what you wrote:
int cnt()
/* counter */
{
int bar() {
return (static int x = 0)++;
}
return bar();
}
Note the difference in return type: cnt() invokes bar() to
get an int result. Since bar is auto, new storage will
be allocated for x on each call to cnt(), which is the
behavior you observed.
Changing the lifetime of x from static to global does, as
you observed, make x act like a counter: subsequent
re-evaluations of the definition of bar don't allocate new
storage for x.
int cnt2()
/* shared counter */
{
int bar() {
return (global int x = 0)++;
}
return bar();
}
(If you reload or re-type-in the definition of cnt2(), it
will start from 0 again. This is because you effectively
define a "new" cnt2() the second time, and it has its
own new "bar()". In a statically scoped world, names don't
uniquely identify storage.)
Finally, if you modify cnt() by making the definition of bar
static, you'll also get a useful counter.
int cnt3()
/* shared counter */
{
static int bar() {
return (static int x = 0)++;
}
return bar();
}
By now, hopefully, the reasoning is obvious.
Somebody should probably put some of the above into
the tutorial. Please let me know if you have further
questions.
Bart
In message <Pine.GSO.4.58.0411062157370.20308 at adara.cs.pdx.edu> you wrote:
> I am reading through the tutorial on the nickle.org site I was
> wondering if you could explaining the difference between the two following
> code samples reproduced below:
> 1)
> int() function incrementer () {
> return (func () {
> static int x = 0;
> return ++x;
> });
> }
> int() a = incrementer();
> int() b = incrementer();
>
> 2)
> function foo () {
> function bar () {
> global g = 1;
> static s = 1;
> g++;
> s++;
> return (int[2]) { g, s };
> }
> return bar ();
> }
>
> So in 1) when a or b is called each has its own independent x, but
> when using foo() only g gets incremented and s is always 2. I am not sure
> how to understand the difference?
More information about the Nickle
mailing list