Reputation: 237
I found out that using a C compiler the code below works but not with a C++ compiler. I understand that casting to void**
is the correct usage but I can't understand why it compiles with the C compiler even if I use the void*
(commented out).
#include <stdio.h>
int fn(void **arg)
{
int *pvalue = *(int**)arg;
*pvalue = 200;
return 0;
}
int main()
{
int value = 99;
int *pvalue = &value;
// fn((void *)&pvalue); // works only in C
// error C2664: 'int fn(void **)': cannot convert argument 1 from 'void *' to 'void **'
fn((void **)&pvalue); // correct, works for both C/C++
printf("%d", value);
return 0;
}
Can someone explain why this is the case?
Upvotes: 1
Views: 287
Reputation: 134066
For avoidance of doubt, there is nothing correct in
fn((void **)&pvalue);
It is just as incorrect as
fn((void *)&pvalue);
The correct way to use the API is to do
int fn(void **arg)
{
int *pvalue = (int *)*arg;
*(int *)pvalue = 200;
return 0;
}
or
int fn(void **arg)
{
*(int *)*arg = 200;
return 0;
}
with
int main()
{
int value = 99;
void *pvalue = (void*)&value;
fn(&pvalue);
printf("%d", value);
return 0;
}
You're not allowed to access an object using any other pointer type, other than the declared type, compatible type, or a character type. Furthermore, while void *
is used as a generic pointer type to all sorts of objects in C, there is no generic pointer to a pointer type in C - other than void *
!
And this is the reason why the void **
is almost always a sign of a design error in APIs - most usages are just wrong.
Upvotes: 3
Reputation: 238461
I can't understand why it compiles with the C compiler even if I use the void* (commented out).
It compiles because void*
is implicitly convertible to other pointers in C.
fn((void **)&pvalue); // correct, works for both C/C++
This may be well-formed because of the cast, the standard doesn't technically give explicit guarantee that conversion to void**
and back yields the same address.
While this may be likely to work in practice, there is no reason to not use void*
as the function argument instead, which does have the guarantee. As a bonus, you won't need the cast in the call. Like this:
int fn(void *arg);
fn(&pvalue); // correct, works for both C/C++
Of course, this is assuming type erasure is needed in the first place. Avoid void*
when it is not needed.
Upvotes: 3
Reputation: 311146
In C there is allowed to assign a pointer of the type void *
to a pointer of other type. This takes place in this call
fn((void *)&pvalue)
where the argument has the type void *
that is assigned to the function parameter that has the type void **
.
int fn(void **arg)
{
int *pvalue = *(int**)arg;
*pvalue = 200;
return 0;
}
However such an assignment in general is unsafe. For example the value of a pointer of the type void * can not be properly aligned to be assigned to a pointer of other type.
So it was decided to not allow such an assignment in C++ to make programs more safer.
Upvotes: 5