In article <11**********************@g14g2000cwa.googlegroups .com>
sa*********@gmail.com <sa*********@gmail.com> wrote:
I wanted this [code] to be [fool]proof ... [and thus catch
uninitialized variables]
There is really nothing special about pointers in this regard.
Consider a simple function int f(int x) that returns (x + 1) mod 5,
i.e., counts 0, 1, 2, 3, 4, 0, 1, .... We can write this as:
int f(int x) {
++x;
if (x >= 5)
x -= 5;
return x;
}
But if you call f() with an uninitialized variable, you may get
an out-of-range result: f(1234) produces 1230, and f(-7) produces
-6. So:
void g(void) {
int i; /* note lack of initializer */
i = f(i); /* ERROR, UNPREDICTABLE */
...
}
Suppose you attempt to catch this with:
int f(int x) {
if (x < 0 || x > 4)
panic("f: x = %d: out of range", x);
++x;
if (x >= 5)
x -= 5;
return x;
}
This will in fact catch many "bad" calls to f() -- but in g(), if
the uninitialized i "just happens" to be 2, it will look, to f()
at least, like a perfectly valid value.
The same holds for pointers:
void h(void) {
char *p; /* note lack of initializer */
p = f2(p); /* ERROR, UNPREDICTABLE */
...
}
Inside f2(), the pointer argument may well "look invalid" under
some (machine-dependent) inspection technique -- just as an out of
range value of x in f() can be discovered -- but it might also
"look valid", just as x might be equal to 2 despite being uninitialized.
Now, a good compiler may produce a warning for the call to f() in
g(), or the call to f2() in h, because the compiler can tell that
the variables in question (i and p respectively) have never been
assigned a value before their value is used. No diagnostic is
*required*, but a good compiler should be able to provide one,
because the error is quite obvious even to a simple mechanical
analysis.
Unfortunately, if we start passing the addresses of the variables
(instead of their values), this simple analysis breaks down. Here
is a revised f() and g(), for instance:
void f2(int *xp) {
int x = *xp;
if (x < 0 || x > 4)
panic("f: x = %d: out of range", x);
++x;
if (x >= 5)
x -= 5;
*xp = x;
}
void g2(void) {
int i; /* note lack of initializer */
f2(&i); /* ERROR, UNPREDICTABLE */
...
}
Here the compiler can no longer use the trivial, local-only,
mechanical analysis technique to discover that the un-initialized,
non-existent value in "i" is being passed. f2() gets a pointer to
i, so it is able to change i, so there are versions of f2() that
would make the call from g2() legal. For instance, if we rewrite
f2() as:
void f2(int *xp) { *xp = 42; }
the call in g2() is now "legal" or "correct".
There are languages (see Ada) in which one annotates one's function
parameters as to whether a reference ("pointer") is "in", "out",
or "in out": whether the original value is used ("in"), and whether
a new value is stored ("out"). If the original value is used *and*
a new value is stored, the parameter is "in out". If C had this,
we might write:
void f2(in out int *xp) {
...
}
and then a good compiler *could* use simple local mechanical analysis
to produce a warning for g2(). But C does not have this, so if
you want good compilers that use only simple local mechanical
analysis to warn about uninitialized values of parameters, you must
take the actual values as arguments, rather than a pointer to the
variable that stores the value.
Of course, nothing says that a C compiler has to do simple local
mechanical analysis (or even be "good", for that matter). A "very
good" compiler that does interprocedural analysis would be able to
discover that *xp is "in out" and warn you that the call from g2()
is invalid. A "bad" compiler that does no analysis at all will
not warn you even with the "obvious" bad call to f() from g().
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.