(Burton Samograd is correct. Note that when GCC is requested
to compile either Standard C89 or Standard C99, it does complain
about attempts to do pointer arithmetic of any sort on "void *".)
Note that:
void *a; ..... a++; leads to a warning from gcc, but
void **a; .....a++; does not.
That is because the C Standards forbid the former (arithmetic on
"void *"), but allow the latter (arithmetic on a pointer that points
to "void *", i.e., values of type "void **").
Are the following identical semantically?
1)
void *a;
a = &foo;
a = (void **)a+1;
2)
void **a;
a = &foo;
a++;
Let me rewrite these as differently-named variables, so that
we can talk about "a" and "a" and not be confused as to whether
"a" means "a", or means instead "a".
I will rename the
first one "v1" and the second one "v2".
Consider a hypothetic machine that has 128-byte "void *"s, but
4 or 8 byte "int *", "double *", "struct S *", and so on. That
is, almost every pointer is just 4 or 8 bytes, as they are on
most machines with which most programmers are familiar -- but
for some reason (perhaps random stubborn-ness), the C compiler
writer chose to make "void *" *much* bigger.
In this case, "void *v1" declares a 128-byte pointer, and
sizeof(v1) is 128. Those 128 bytes hold some value(s); as
required by Standard C, after:
void *v1 = &foo;
the 128 bytes hold enough information to locate the variable
"foo", no matter what (data) type "foo" has (we can assume
"foo" is not a function name here).
On the other hand, "void **v2" declares an ordinary 4 or 8 byte
pointer, so that sizeof(v2) is 4 or 8. If we are lucky -- it is
not clear whether this is "good luck" or "bad luck" -- and we
write:
void **v2 = &foo;
and it compiles at all and runs, "v2" will also hold enough
information to locate the variable "foo". The C standards do
not *guarantee* this unless "foo" has type "void *" in the
first place, though, because whatever type "foo" has, &foo
produces a value of type "pointer to ____" (fill in the blank
with foo's type). The variable v2 has type "void **" and thus
can only point to "void *"s.
Let us get a little more concrete about our target machine, and
declare that, on our target machine, "void *" is 128 bytes, and
"int *" is 8 bytes, but "double *" is just 4 bytes. The machine
does this because it has a maximum of 32 gigabytes of RAM. Its
"int"s are 4 bytes long and always aligned on a 4-byte boundary,
but this means that they could be at any of 8,589,934,592 possible
locations, so we need 33 bits to address them. Its "double"s are
8 bytes long and always aligned on an 8-byte boundary, so they
can only be at any of 4,294,967,296 possible locations -- 32
bits suffices to address those.
"void *" remains 128 bytes because of the compiler-writer's whim,
apparently. But, because these are always aligned on a 128-byte
boundary, "void **" only needs to represent 268,435,456 distinct
values (28 bits), so "void **" is just 4 bytes, like "double *".
Now let us suppose that "foo" has type "int":
int foo;
void *v1 = &foo;
void **v2 = (void **)&foo;
The cast is needed here to force the compiler to accept the
conversion: &foo has type "int *" -- which is 8 bytes long -- and
we use the cast to squeeze it through a knothole, whacking off 4
of the 8 bytes, to put it into a "void **".
Having done all of this, "v1" definitely points to "foo" --
128 bytes of "void *" have plenty of room for 8 bytes' worth
of pointer value -- but "v2" might well not point to "foo" at
all, having lost some important bit(s).
Now, if we do:
v1 = ((void **)v1) + 1;
this takes the 128-byte long value in v1 -- which contains the
exact 8-byte address of the variable "foo", along with a bunch more
bytes that are not very interesting -- and scrapes it through the
knothole, producing a 4-byte value of type "void **" and discarding
the remaining 124 bytes. 120 of those 124 were probably useless,
but 4 of them might have held important data. Those data are gone!
Next, this adds 1 "void *"'s worth to the value computed so far.
Since sizeof(void *) is 128, this effectively adds 128 to the
poointer produced by scraping 124 bytes off the value in v1. Then
this result is converted back to "void *" -- adding back 124
bytes, but probably not restoring the lost data -- and that
result is stored back into "v1". Of course, this being all one
big expression, and the effect of "scraping off" value bits not
being defined by the C Standards, it is possible this *does*
restore the lost data, so that v1 points to "128 bytes past
the variable foo".
If we do:
v2++;
then we take the (not well defined) value in v2 -- the result of
squeezing &foo through that same knothole -- and increment it,
pointing to the next "void *" to which v2 points. Since v2 does
not point to "void *"s, the effect continues to be undefined,
but this probably effectively adds 128 to v2.
In other words, the two have similar effects -- but "v1" was
at least guaranteed to start out as valid, while v2 had no
guarantees at all.
Note that if we made "foo" have type "void *", the picture
changes. In this case, &foo has type "void **" -- pointer to
pointer to void -- which fits in v2 just fine, because v2
has type "pointer to pointer to void" and can thus point to
any "pointer to void". In this case, "v2++" is well-defined
-- it makes v2 point "one past the end of the array", where
"the array" is the "implied" array of size 1 that the Standards
guarantee for ordinary variables. Since v2++ is well-defined,
(v1 = (void **)v1 + 1) is also well-defined and then *does*
do the same thing.
I feel a little uneasy declaring things as void **, (or void ***), but
it seems to work just fine. I'm not sure why I feel uncomfortable with
it. Is it safe and valid?, or should the cast be used?
It may, I think, help to think of "void *" as a special type
(because it *is* in fact a *very* special type). It may even
help a bit further to use a typedef for it:
typedef void *Generic_Ptr;
Now we have an alias named Generic_Ptr that can be used to declare
variables (and structure members and so on):
Generic_Ptr p1;
Generic_Ptr p2;
Generic_Ptr arr[100];
Of course, given any object (loosely, "variable") of some type T,
we can always, in C, construct a value of type "pointer to T", and
declare variables suitable to hold such values. So if p1, p2,
and arr
are "Generic_Ptr"s, we can point to any of them:
Generic_Ptr *q;
...
q = &p1;
...
q = &p2;
...
q = &arr; /* for some valid index "i" */
As with any pointer in C, "q" can be NULL, or it can point to a
single instance of a Generic_Ptr (like p1 and p2), or it can point
into an array (like &arr). If it points to the first element
of an array:
q = arr;
then q "means" the same thing as arr. This is just like
any other pointer in C:
int iarr[100];
int *ip = iarr;
/* now ip is just like iarr */
char carr[100];
char *cp = carr;
/* now cp is just like carr */
And just as we can do things with "*ip++" and "*cp++" to step
through iarr[] and carr[], we can do things with "*q++" to step
through arr[], our array of "Generic_Ptr"s. All we have to
do is set elements of that array as appropriate:
arr[0] = &this_var;
arr[1] = &that_var;
arr[2] = &the_other_var;
arr[3] = NULL; /* marks the end */
for (q = arr; *q != NULL; q++) {
... do something with *q ...
}
Of course, typedefs do not actually define new types, so "q" has
type "void **" -- q has type "pointer to Generic_Ptr", but "Generic_Ptr"
is just another name for "void *".
Note that "void **" is *not* generic; it is an ordinary, non-special
pointer. It just happens to point *to* a special kind of pointer.
[pause here, if you like ]
The "void ***" type in C is just like any other triple-pointer: it
can be NULL, or can point to a single "void **", or can point into
an array of "void **"s. If "q" has type "void **", then we can
define a "pq" thus:
void ***pq = &q;
Now *pq is just another way to name "q", and if we do:
q = arr;
then q names arr as before.
Most often, triple pointers in C like "qp" come about when we need
to write a function that sets a variable like "q".
For instance, suppose we start with a function that creates an
array of "char *"s:
char **make_arr(size_t n) {
char **space;
size_t i;
space = malloc(n * sizeof *space);
if (space == NULL) ... do something here ...
for (i = 0; i < n; i++)
space = NULL;
}
This function is reasonably straightforward and might be used to
build an "argv" array. Of course, each argv also has to be
set to some useful value, not just NULL -- only the last one is
NULL -- so we might augment it a bit further:
for (i = 0; i < n - 1; i++)
space = malloc( ?? );
space = NULL;
We still need to figure out how much space to allocate, i.e.,
to fill in the "??" part. We also need to handle the case where
we run out of memory (at least one of the malloc()s fails).
(Those paying particularly close attention should have noticed
by now that, with argc and argv, we have argv[argc]==NULL, not
argv[argc-1]==NULL. So make_arr() is not *quite* the same as
working with argv -- we would have to pass in argc+1.)
As the code progresses, we might eventually find ourselves
wanting to write a sub-function that takes "&space" and does
the malloc()-ing. Well, "space" has type "char **" here, so
&space has type "char ***".
If instead of an argv[]-like array, we want an arr[]-like array of
"void *"s, then we would have a "void **space", and if we decided
to put the allocation in a sub-function that takes &space, the
sub-function would have anargument of type "void ***".
Note, again, that it is ONLY the type "void *" that is special:
"void **", "void ***", and even "void ****" or "void *****" is just
ordinary pointer types, and obey all the normal rules for pointers
in C. The "void *" type is the ONLY "special" one, and its
"special-ness" is limited to ordinary assignments and those things
that emulate them (argument passing with prototypes, and -- because
casts are just very forceful assignments -- casts).
The moral, as it were, of the all this is that "void *" can point
to any data type, but "void **" can only point to "void *"; and
"void ***" can only point to "void **"; and so on. You can let
yourself think of "void *" as a special case, because it is; but
do not allow this to lead you into thinking that other types are
special too.