From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,cef1968b544ddf26 X-Google-Attributes: gid103376,public From: dewar@merv.cs.nyu.edu (Robert Dewar) Subject: Re: Static variables? Date: 1997/03/19 Message-ID: X-Deja-AN: 226732861 References: <332D71FF.4773@cae.ca> Organization: New York University Newsgroups: comp.lang.ada Date: 1997-03-19T00:00:00+00:00 List-Id: 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.