Markus Gilli
Markus Gilli

Reputation: 237

C vs. C++, handling of void** pointers

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

Answers (3)

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

eerorika
eerorika

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

Vlad from Moscow
Vlad from Moscow

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

Related Questions