Re: The void** pointer breaking symmetry?



elmar@xxxxxxxxxx schrieb:
Thanks for your detailed reply, Keith.

Of which you unfortunately did not quote anything to provide
context.

In my humble view, the ideal solution would be:

1) void* is a generic pointer type that can be implicitly converted
to/from any other object that can be dereferenced at least !once!
(e.g. char*, int*, also char**, int**, but not char,int)
2) void** is a generic pointer type that can be implicitly converted
to/from any other object that can be dereferenced at least !twice!
(e.g. char**, int**, also char***, int***, but not char*,int*,char,int)
3) etc...

Again, I am not one of those who like to (have the time to) discuss
philosophic questions about language details. This is a purely
practical issue, having identified the main cause of entropy (and also
crashes ;-) in my current sources: The inability to safely pass a
pointer to any pointer as a function argument.

The problem with this approach is that if, for example, char *foo,
int *bar, and struct qux *quux have different sizes, alignment
requirements and representations, then there is no way that
void **baz (containing either the address of foo, bar, or quux, or
of a variable containing their addresses, or ...) helps you
deal with the object(s) they point to if you pass the addresses of
such pointers, because dereferencing the void ** variable once gives
you at least three possible interpretations of the bit pattern
involved. The only safe way to obtain the address of the object would
be if *baz would evaluate to a type which can contain any address that
can be pointed to by any of, foo, bar and quux. One such type is void*.
So,
void *aux = bar;
void **baz = &aux;
essentially is the best you can get if you do not want the language
to have to "remember" types at runtime.


Typical example: the function mem_freesetnull which frees a pointer and
sets it to NULL (very helpful in the context of exception handling):

Ideally, it would take the address of a pointer as argument and look
like that:

void mem_freesetnull(void **ptradd)
{ mem_free(*ptradd);
*ptradd=NULL; }

Unfortunately, that's not possible, because I'd have to use an ugly
explicit cast to (void**) in every call to the function.

So in practice, I have to move the explicit cast to the function
itself:

void mem_freesetnull(void *ptradd)
{ mem_free(*(void**)ptradd);
*(void**)ptradd=NULL; }

Now the function looks ugly, but more importantly, I lost an important
piece of type-safety:
If I accentally forget the reference operator & in the function call,
noone will complain but the program will crash:

char *cp;
cp=mem_alloc(1000);
mem_freesetnull(cp); /* Crash! */

Even
mem_freesetnull(&cp)
works portably only due to special guarantees for char*.
If you did the same for int *ip or struct qux *sp, you could run
into trouble on a system where not all pointers are equal.


With the approach suggested above, the compiler would immediately
identify the problem, since cp cannot be dereferenced at least 2 times,
as required by the ideal function declaration
void mem_freesetnull(void **ptradd).

Not portably if you do not want to change the information the
programme must have at runtime -- and then you could change other
restrictions of C as well.

In short: less code entropy and more safety in one shot.

At a very high price for the language.

In addition, not every place the now-stale address is stored will
be "nulled" by this function -- it is more or less a false sense
of safety. Realloc()ing to zero or even to a smaller size of the
originally malloc()ed storage gives you similar headaches.

Having a way to mark the starting address of the allocated object
and all addresses inside or one past the object as trap
representation with ways to find out where the whole thing trapped
would be much more useful.
Malloc debugging tools already do at least part of the job for you.
Just add "runs cleanly under <YourToolHere> for the test set" after
"compiles without warning" and "is <YourLintToolHere>-clean".


It seems that
others are bothered by the same thing, since this in the FAQ:

4.9: Can I use a void ** pointer as a parameter so that a function
can accept a generic pointer by reference?

A: Not portably.

(not sure what the answer means in this context. Does it cause problems
on a VAX from 1968? ;-)

I'm thinking about a GCC patch for an option to specifically disable
the warning in the cases outlined above. But if that has zero chance of
acceptance, I'll save my time ;-)

As I did not read what you really want to achieve (you snipped the
context), I do not know whether this is the perfect solution for you
or just the bad idea it seems to be...


Cheers
Michael
--
E-Mail: Mine is an /at/ gmx /dot/ de address.
.



Relevant Pages

  • Re: confusion: casting function pointers
    ... pointer from the 'actual/other modules' that takes arguments of type ... list to types of void *). ... int main{ ... without a prototype, a number of special "promotion" rules take ...
    (comp.lang.c)
  • Re: Type-checking casts for GNU C
    ... I don't think *x is valid when x is a void*, ... operand must have pointer type. ... But why would it allow dereferencing of a void* (even when its "treat ... the other cast macro, T must be a pointer type in this one, so you just ...
    (comp.lang.c)
  • Re: [RFC] timers, pointers to functions and type safety
    ... * they have callback of type void ... callback is called by the code that even in theory has no ... cast to unsigned long and cast back in the callback. ... number - not a pointer cast to unsigned long, not an index in array, etc. ...
    (Linux-Kernel)
  • Re: Can I Trust Pointer Arithmetic In Re-Allocated Memory?
    ... You can't do pointer arithmetic on a void* value. ... qsort behaves in a manner consistent with its specification. ... actually works as advertised...but doesn't mean that the cast is ...
    (comp.lang.c)
  • Re: (maybe DR) lvalueness of dereferencing void*
    ... >> the definition of lvalue (6.3.2.1p1; lvalues don't have void type). ... > A void pointer does not point to an object and dereferencing it does ...
    (comp.std.c)