comp.lang.ada
 help / color / mirror / Atom feed
From: dewar@merv.cs.nyu.edu (Robert Dewar)
Subject: Re: Static variables?
Date: 1997/03/19
Date: 1997-03-19T00:00:00+00:00	[thread overview]
Message-ID: <dewar.858770825@merv> (raw)
In-Reply-To: JSA.97Mar17193855@alexandria


Jon said (answering question about static variables)

package P is

    V: Some_Variable_Type ....
    procedure P1;


Robert notes:

This is not good advice, or at least does not answer the question. The
idea of static variables (e.g. in Algol60 or C) is to create a variable
that can only be referenced by the procedure, but which retains its value
from call to call.

You certainly do not map such a facility by putting variables in the package
spec, which can be accessed by clients.

A much closer approximation is to place the variable in the package body,
where it cannot be seen by a client. Note that in Ada 95, the variable
definition can go right next to the body (this is one of the good arguments
for dropping the annoying basic-later ordering restrictions on declarations
in Ada 83).

The closest approximation is

package body P is

     package P1_Package is
        procedure P1_Proc;
     end P1_Package;

     package body P1_Package is
        V : Some_Variable_Type ...
        procedure P1_Proc;
     end P1_Package;

     procedure P1 renames P1_Package.P1_Proc;

     ...


and now the variable V is not directly visible even in the body of package
P, but for most purposes this is probably overkill, since simple naming
conventions and discipline can presumably be trusted at the single package
level, it is clients who must be stopped from fiddling with the variable
directly.

Note that the whole idea of static variables is fundamentally inconsistent
with task safety, since the variable V here is almost certainly going to be
a shared variable which is in danger of generating erroneous behavior
(RM 9.10(11-15)). 

Possible solutions are

  Make V a protected object (i.e. of a protected type), and get and set its
  value with protected calls. This is likely to be very expensive. On bare
  board implementations, protected types can be made quite efficient, but
  when operating over an operating system, protected object semantics are
  heavy, and protected calls are likely to result in multiple kernel calls.

  Make P1 a generic, with the understanding that separate tasks should
  instantiate separate versions, each of which will have its own variable
  V. 

  Give the caller a handle to V, which is then passed as a parameter, with
  a constructor, and the understanding that different tasks do not share the
  same handle (see Ada.Numerics.Discrete_Random for an example of this 
  approach).

  Make V atomic, so that updates are task safe.

Solution 1 (the protected object) will always work, but might be expensive.
Solutions 2-3 are appropriate only if it is OK to have multiple V's (the
random number case is an archetype of this case).

SOlution 4 works only if the operations on V are limited to simple loads
and stores (e.g. a First_Time_In Boolean would work, although you have
to be prepared for race conditoins, so that two tasks could both think
it was the first time in. If you need to do x := x + 1, then the atomic
solution creates additional race conditions.

Note that in solution 1, to avoid race conditions, you need to take 
operations like x := x + 1 inside the protected type.

Making things task safe is not easy at all if you have state of this kind
that must be retained from call to call and if you want efficiency.

Let's consider for example the issue of a memoized Ackerman function.
we really do not want a protected call on every call to Ackerman, since
in typical Unix systems, these calls can be distressingly inefficient,
and one can find that protected calls are comparable to task switching
in overhead. This is a fundamental property of the way that protected
objects were designed (e.g. to respect ceiling priorities accurately)
and it was known during the design that the choices made would result
in a relatively heavy locking mechanism when working over an operating
system (e.g. a protected call cannot use a simple test-and-set lock,
since this would cause possible priority inversions in some unlikely
worst case scenarios).

Back to Ackerman. One approach is to have two (two-D) memory arrays, one
has booleans showing if a value is set, the other has the values. Let's
assume that the values are large composites representing extended precision
values, too big to be atomic.

If a boolean is set, we know we can access the value, without a lock, and
all is safe.

If a boolean is not set, then we take a lock (i.e. we do this in a protected
object) and set the value in place.

This approach means that at least we only pay the price of a lock on the
set, not on every access. Of course if the values to be stored are small
enough to be atomic, then we can just store them and retrieve them directly
(this is a case of solution 4 above).

Code that casually ignores task safety is often very hard to fix up so that
later on it is task safe, without introducing lots of inefficient locks.
This is the problem that is often faced in making C runtime libraries
safe, although in C the problem is made much worse by not having nested
functions, so that one ends up using static variables gratuitously, which
in Ada or Pascal would be up level (task safe) references.

And that by the way is a reminder to C programmers writing in Ada. Do NOT
use global variables gratuitously. For example, suppose you are writing
a scanning package in Ada, where internally there is a scan pointer variable
that is only needed for one outer level client call, but the operation is
complex, and there are many internal procedure calls in the package body.

There are three basic approaches:

1. Pass the scan pointer around everywhere as an in-out parameter, this can
   add a lot of junk to the code.

2. Use a static variable which is set on entry, and then referenced by the
   various procedures (in Ada, the static variable is a package body variable)

3. Make the variable be a local variable of the outer procedure and nest all
   other procedures within this procedure. 

Assuming you are not explicitly using tasking, approach 3 is task safe, but
may not easily occur to C programmers writing in Ada, because they are not
used to writing nested procedures (note the phrasing of the original
question -- when anyone asks "How can I do X in language Y", where X is
a technical term from some other language, alarm bells should go off, because
it is likely that someone is trying to port an unnatural design.

In other words, the best answer to the question "How do I get a static
variable in Ada" is often "Just say NO to static variables".

In general, library level variables in package specs or package bodies
raise task safety issues. A reasonable protocol in an environment where
task safety has to be considered is that EVERY such variable should be
annotated with comments discussing its task safety.







  parent reply	other threads:[~1997-03-19  0:00 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
1997-03-17  0:00 Static variables? Viqar Abbasi
1997-03-17  0:00 ` Robert Dewar
1997-03-18  0:00   ` nasser
1997-03-18  0:00 ` Jon S Anthony
1997-03-18  0:00   ` Samuel Tardieu
1997-03-18  0:00     ` Tom Moran
1997-03-19  0:00   ` Robert Dewar [this message]
1997-03-25  0:00     ` Richard A. O'Keefe
1997-03-25  0:00       ` Larry Kilgallen
1997-03-27  0:00         ` Robert Dewar
replies disabled

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