[comp.lang.c] Problem with use of 'void **'

darcy@druid.uucp (D'Arcy J.M. Cain) (05/26/90)

In article <1990May25.012342.10144@csis.dit.csiro.au> peterf@csis.dit.csiro.au (Peter A Fletcher) writes:
>
>I would like to be able to write a routine which can pass
>a block of memory back to the caller, so the obvious way
>to do this is by passing a pointer to a pointer of any
>type - 'void **' seems the natural way to do this (using
>'void *' would allow passing addresses of non-pointers,
>which is a definite no-no).
>
Doesn't seem natural at all.

>Here's an example:
>
>#include <stddef.h>
>#include <stdlib.h>
>
>void problem(void **a, int l)
>{
>    *a = malloc(l);
>}
>typedef char fiftychars[50];
>int main(int argc, char *argv[])
>{
>    fiftychars *a;
>    problem(&a, 50);
>    return 0;
>}
>void.c: In function main:
>void.c:18: warning: argument passing between incompatible pointer types

First of all the typedef says that an array of fifty character is created
when fiftychars is declared.  However, you declare a to be a *pointer* to
this array of 50 chars.  In effect your declaration has become:
    char	**a;
since the 50 characters have not actually been allocated.  Note that you
*must* have the extra size parameter for problem.

Since a is char **, &a is char ***.  That is why the type mismatch.  The
call should be "problem(a, 50);"  However the whole code seems clumsy
aside from all that.  I would code something like the following.  Note
that I am assuming that problem is not as trivial as your stripped down
example indicates so I have not reduced the call to problem to a call to
malloc.

#include <alloc.h>
void *problem(int l)
{
    return(malloc(l));
}
int main(int argc, char *argv[])
{
    char *a;
	a = problem(50);
    return 0;
}

Followups to comp.lang.c

-- 
D'Arcy J.M. Cain (darcy@druid)     |   Government:
D'Arcy Cain Consulting             |   Organized crime with an attitude
West Hill, Ontario, Canada         |
(416) 281-6094                     |

raeburn@athena.mit.edu (Ken Raeburn) (05/27/90)

In article <1990May25.012342.10144@csis.dit.csiro.au>
peterf@csis.dit.csiro.au (Peter A Fletcher) writes:
|> >
|> >I would like to be able to write a routine which can pass
|> >a block of memory back to the caller, so the obvious way
|> >to do this is by passing a pointer to a pointer of any
|> >type - 'void **' seems the natural way to do this (using
|> >'void *' would allow passing addresses of non-pointers,
|> >which is a definite no-no).

That does seem like the logical way to do it, assuming the return value of
the routine is used for some other purpose.

|> >void problem(void **a, int l)
|> >{
|> >    *a = malloc(l);
|> >}
|> >typedef char fiftychars[50];
|> >int main(int argc, char *argv[])
|> >{
|> >    fiftychars *a;
|> >    problem(&a, 50);

|> >void.c:18: warning: argument passing between incompatible pointer types

In article <1990May26.011714.7624@druid.uucp>, darcy@druid.uucp (D'Arcy
J.M. Cain) confuses the issue by writing:

|> First of all the typedef says that an array of fifty character is created
|> when fiftychars is declared.  However, you declare a to be a *pointer* to
|> this array of 50 chars.  In effect your declaration has become:
|>     char	**a;
|> since the 50 characters have not actually been allocated.  Note that you
|> *must* have the extra size parameter for problem.

... and continues to get more confused from there.

Chris Torek posted a good detailed article a little while ago about
arrays.  I don't have a copy handy, but I'll try to briefly explain.  For
purposes of explanation, I will use "fiftyints" instead of "fiftychars";
some similarities between "void *" and "char *" are required, but they are
distinct, and the similarities can cause confusion.

In main:
	*a	is	array[50] of int
	a	is	ptr to array[50] of int
	&a	is	ptr to ptr to array[50] of int
In problem:
	a	is	ptr to ptr to void

The "char **" explanation is comparable to saying that by modifying *a,
one could modify the address of the array.  This doesn't make sense, but
neither does the conversion of array to pointer in this context.  The
variable "a" is not pointing to a pointer, but to an array.

(It's sort of akin to the error of claiming that "int x[3][3]" and "int
**x" are equivalent.)

The general reason for requiring the type mismatch is that the pointers
involved are pointing to

	foo *
and	void *

which can have different representations.  If "int *" and "void *" are
represented differently, then converting between the two is a (presumably)
simple operation that the compiler can incorporate.  However, "int **" and
"void **" are not at all compatible, since you'd wind up with a pointer of
type "void **" that points to memory in which you want to store a pointer
in "int *" format (or vice versa), and that information cannot be
maintained.

Although "char *" and "void *" are pretty much constrained to have the
same representation, they aren't exempted from this compatibility
constraint.

The fix I would suggest would be:

	void problem (void **a, int l) {
		*a = malloc (l);
	}
	typedef char fiftychars[50];
	int main () {
		fiftychars *a;
		void *vp;
		problem (&vp, 50); a = vp;
		/* ... */
	}

or something similar....

Chris, as I recall, your article explained things quite clearly; do you
have a copy you can repost?

darcy@druid.uucp (D'Arcy J.M. Cain) (05/28/90)

In article <1990May26.203219.10343@athena.mit.edu>
Ken Raeburn <Raeburn@MIT.Edu> writes:
>
>In article <1990May25.012342.10144@csis.dit.csiro.au>
>peterf@csis.dit.csiro.au (Peter A Fletcher) writes:
>
>|> >void problem(void **a, int l)
>|> >{
>|> >    *a = malloc(l);
>|> >}
>|> >typedef char fiftychars[50];
>|> >int main(int argc, char *argv[])
>|> >{
>|> >    fiftychars *a;
>|> >    problem(&a, 50);
>
>|> >void.c:18: warning: argument passing between incompatible pointer types
>
>In article <1990May26.011714.7624@druid.uucp>, darcy@druid.uucp (D'Arcy
>J.M. Cain) confuses the issue by writing:
>
>|> First of all the typedef says that an array of fifty character is created
>|> when fiftychars is declared.  However, you declare a to be a *pointer* to
>|> this array of 50 chars.  In effect your declaration has become:
>|>     char	**a;
>|> since the 50 characters have not actually been allocated.  Note that you
>|> *must* have the extra size parameter for problem.
>
>... and continues to get more confused from there.
OK, I assumed that "fiftychars *a" was equivalent to "char *a[50]" since
"fiftychars a" would be the same as "char a[50]."  This would have made
a an array of 50 pointers to char.  That means that a would be a pointer
to a character array.  So then I tried to think of what else it could mean.
It didn't seem reasonable that it could mean that a was a pointer to an
array of 50 characters unless the 50 characters already existed in which
case why malloc them?  At that point I wrote a little test program and
studied the assembler output to see if I could glean the meaning.

Well, according to AT&T, Borland and GNU, the value of a is the same as *a.
A little thought shows why this is true.  The declaration creates an array
of 50 characters and creates a pointer to them which is what a is.  Since
a is a pointer, it can be assigned to which is what happens when problem
is called.  At this point the array that was originally created is still
in memory but is inaccessible to the program.  I can't help but feel that
this is not was the original poster had in mind.

-- 
D'Arcy J.M. Cain (darcy@druid)     |   Government:
D'Arcy Cain Consulting             |   Organized crime with an attitude
West Hill, Ontario, Canada         |
(416) 281-6094                     |