Why is (void **) not compatible with (foo **) ?

N

Noob

Hello,

I know (void **) is not "compatible" with (foo **) but I'm wondering why.

I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

Why is a pointer to v not be compatible with a pointer to i?

Case in point, I'm using a library where (for consistency) every
function returns an error code, even the memory allocation function.

So I'm dealing with
int mem_alloc(size_t size, void **p);

Using malloc, I can just write
foo *pf = malloc(sizeof *pf);

but with this library, I can't write
foo *pf;
int err = mem_alloc(sizeof *pf, &p);

I have to write
foo *pf; void *pv;
int err = mem_alloc(sizeof *pf, &pv);
pf = pv;

Or is there a better, simpler, clearer idiom?

Regards.
 
R

Richard Damon

Hello,

I know (void **) is not "compatible" with (foo **) but I'm wondering why.

I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

Why is a pointer to v not be compatible with a pointer to i?

Case in point, I'm using a library where (for consistency) every
function returns an error code, even the memory allocation function.

So I'm dealing with
int mem_alloc(size_t size, void **p);

Using malloc, I can just write
foo *pf = malloc(sizeof *pf);

but with this library, I can't write
foo *pf;
int err = mem_alloc(sizeof *pf, &p);

I have to write
foo *pf; void *pv;
int err = mem_alloc(sizeof *pf, &pv);
pf = pv;

Or is there a better, simpler, clearer idiom?

Regards.

The biggest part of the issue is that pointers to different types may be
represented differently. In particular, a char* pointer may need
additional information on a machine which uses "word" addresses to
indicate which byte in the word to access.

So while you can write a conversion from any type to/from void, this
conversion is not necessarily "trivial". It is true that for most
machines the pointers have the same representation, the standard is
written to allow it to be implemented on a wide variety of machines.
 
J

James Kuyper

Hello,

I know (void **) is not "compatible" with (foo **) but I'm wondering why.

I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

Why is a pointer to v not be compatible with a pointer to i?

There's two ways to answer that, depending upon what you mean by the
"Why". First of all, there's the applicable citations from the standard:
"For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types." (6.7.6.1p2)
Because there's two levels of indirection involved here, that rule gets
applied twice.
Since void and foo are not compatible types (I assume that foo is NOT a
typedef for void), then void* and foo* are not compatible types. If
void* and foo* are not compatible types, void** and foo** are not
compatible types.

The second way to answer that is in terms of the reason why that rule
was written that way. Consider a machine with 16 bit addresses and a
word size of 32 bits. This is just an example chosen to clarify the
reasons behind that decision; machines with precisely that combination
of characteristics (which are probably a bit unusual) are NOT the reason
why this rule was adopted.

C requires that char* pointers uniquely identify bytes. One option would
be to choose CHAR_BIT==32, but a more conventional approach would be to
choose CHAR_BIT==8, and to access char* using pointers that are more
than 16 bits long. 16 of those bits would identify the particular word
containing the char, and 2 additional bits would be used to identify
which particular byte within that word contains the char. On such an
implementation, 3 bytes would be the minimum size of such a pointer, but
it would probably be rounded up to 4 bytes == 32 bits. Some such scheme
must be used for any object that has a size less than the word size -
let's call that the large pointer format. However, if larger types are
always aligned on word boundaries, pointers to those types only need to
contain 16-bit addresses - let's call that the small pointer format.
Problems like this one are the main reason that the standard allows
pointers to different types to have different representations, sizes,
and alignment requirements. This particular problem only requires
pointers of two different formats, but the standard allows an
arbitrarily large number of mutually incompatible pointer formats.

However, the standard does require that a void* have the same
representation and alignment requirements as a char*, so on such a
machine, it must have the large format. On the other hand, if
sizeof(foo)>4, foo* could use the small format.
OK, now let's apply that one more time: since sizeof(void*) == 4, void**
can use the small pointer format. However, if sizeof(foo*)==2, then
foo** must use the large pointer format. Those two formats are
inherently incompatible.
 
Æ

楚蛮人

在 2012å¹´11月8日星期四UTC+8下åˆ6æ—¶08分20秒,Noob写é“:
Hello,



I know (void **) is not "compatible" with (foo **) but I'm wondering why.



I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;



Why is a pointer to v not be compatible with a pointer to i?



Case in point, I'm using a library where (for consistency) every

function returns an error code, even the memory allocation function.



So I'm dealing with

int mem_alloc(size_t size, void **p);



Using malloc, I can just write

foo *pf = malloc(sizeof *pf);



but with this library, I can't write

foo *pf;

int err = mem_alloc(sizeof *pf, &p);



I have to write

foo *pf; void *pv;

int err = mem_alloc(sizeof *pf, &pv);

pf = pv;



Or is there a better, simpler, clearer idiom?



Regards.

I recommend you to read <Expert C Programming>, first chapter explain it
 
B

Ben Bacarisse

Noob said:
I know (void **) is not "compatible" with (foo **) but I'm wondering
why.

The reason is that C allows different pointer types to use different
representations (though there are some restrictions). Take, for
example, a word addressed machine. The efficient representation of,
say, int * is as a word address. void * and char * must still be
representable so let's imagine the implementation chooses to shift the
word address to the left and to use the bottom bits (let's say two of
them) for a byte offset.

With this implementation, a pointer conversion will often generate
code, but if foo ** and void ** were compatible, the required conversion
can't be done:

int x = 42;
int *ip = &x; // let's say ip is the word address 0x100
void *vp = ip; // vp is now the "invented" address 0x400

int **ipp = &ip;
void **vpp = ipp; // NOT PERMITTED -- let's pretend
void *vp2 = *vpp; // what's in vp2? It must be 0x100
assert(vp2 == vp1); // oops!
I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

Why is a pointer to v not be compatible with a pointer to i?

Case in point, I'm using a library where (for consistency) every
function returns an error code, even the memory allocation function.

So I'm dealing with
int mem_alloc(size_t size, void **p);

Using malloc, I can just write
foo *pf = malloc(sizeof *pf);

but with this library, I can't write
foo *pf;
int err = mem_alloc(sizeof *pf, &p);

I have to write
foo *pf; void *pv;
int err = mem_alloc(sizeof *pf, &pv);
pf = pv;

Or is there a better, simpler, clearer idiom?

None comes to mind. If you try a cast

foo *pf;
int err = mem_alloc(sizeof *pf, (void **)&pf);

you are "lying to the compiler". It will work on many implementations,
but it brings up the hair on the back of my neck because I once had to
port various Unix utilities to a word addressed machine. The sort
program was full of code like that. I ended up using marker pens on a
full code listing, colouring in what contained a word pointer and what
contained byte pointer. It was horrible.
 
K

Keith Thompson

Noob said:
Hello,
I know (void **) is not "compatible" with (foo **) but I'm wondering why.

I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

Why is a pointer to v not be compatible with a pointer to i?

Perhaps a better question would be, why *should* they be compatible?

Type "compatibility", as defined by the standard, is a more stringent
concept than assignability. For example, all arithmetic types are
assignable to each other (with implicit conversions where necessary),
but no two distinct arithmetic types are compatible, even if they
happen to have the same representation.

Pointers to distinct types are not compatible. void* and foo*
are distinct types (even though they're assignable, due to special
rules for void*), so pointers to them are incompatible.

If you're actually wondering about assignability rather than
compatibility, in general foo* and bar* are not assignable; there
is no implicit conversion between foo* and bar* (unless either foo
or bar is void, or foo and bar are compatible). If there were,
then you could do things like this:

foo foo_obj;
foo *foo_ptr = &foo_obj;
bar *bar_ptr = foo_ptr; /* invalid */

which would allow to treat foo_obj as if it were an object of
type bar. You *can* do type-punning like this, but it requires an
explicit pointer cast (or a union, or memcpy() on a pointer object,
or ...).

The inability to assign a void** to a foo**, or vice versa, is just
one case of this, where the targeted object types are void* and foo*.
And in particular, it's plausible that a void* could be bigger than
a foo*, or that it could have a different representation; in that
case type-punning would cause serious problems. (On most modern
implementations, all pointers have the same size and representation,
but the C standard is careful not to assume that.)

void* is (more or less) a generic pointer-to-object type. void**
is *not* a generic pointer-to-pointer-to-object type, and in fact
there is no generic pointer-to-pointer-to-object type.
 
O

Old Wolf

I know (void **) is not "compatible" with (foo **) but I'm wondering why.

(void *) and (int *) might be different formats, e.g. I have
programmed on a system where (int *) is 2 bytes and (void *)
is 3 bytes. If you passed a pointer to (int *) to this function
which writes 3 bytes, it'd be a buffer overflow (amongst other
problems).
 
S

Seungbeom Kim

Hello,

I know (void **) is not "compatible" with (foo **) but I'm wondering why.

I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

Why is a pointer to v not be compatible with a pointer to i?

Others have given good practical reasons regarding different
representations of differently-typed pointers. More generally,
allowing such a conversion would allow a hole through which
type safety could be compromised.

// assume A and B are unrelated types
A a, *pa;
B b;
void** ppv = &pa; // assign A** to void** (illegal)
*ppv = &b; // assign B* to void*

In this example, pa is supposed to hold only pointers to A objects,
but ppv pointing to pa allows assigning &b to pa, even without a cast.
Case in point, I'm using a library where (for consistency) every
function returns an error code, even the memory allocation function.

So I'm dealing with
int mem_alloc(size_t size, void **p);

Using malloc, I can just write
foo *pf = malloc(sizeof *pf);

but with this library, I can't write
foo *pf;
int err = mem_alloc(sizeof *pf, &p);

I have to write
foo *pf; void *pv;
int err = mem_alloc(sizeof *pf, &pv);
pf = pv;

Or is there a better, simpler, clearer idiom?

I think the code is fine. Typically, you want to examine the error code
before you actually use the pointer, so the code would look like this:

void *pv;
int err = mem_alloc(sizeof foo, &pv);
if (err indicates an error) {
// handle the error
}
else {
foo *pf = pv;
// use pf
}

Alternatively, you could make mem_alloc return the pointer (if
it's feasible) and let the compiler take care of the conversion:

void *mem_alloc(size_t size, int& err);

int err;
foo *pf = mem_alloc(size_t size, &err);

(You can write a wrapper function if you cannot modify the library.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top