1

With the following snippet:

int n = 11;
int* c = &n;
void** v = &c;

I receive the following error in visual studio:

the value of type int** cannot be used to initialize an entity of type void **.

This works fine:

int n = 11;
int* c = &n;
void* v = c;

But this code snippet is for a larger problem in someone's library.

What am I doing wrong with casting a variable to void**?

Complete Example

Using the caen digitizer library the way they try to collect data from the peripheral device has this prototype:

/******************************************************************************
* X742_DecodeEvent(char *evtPtr, void **Evt)
* Decodes a specified event stored in the acquisition buffer writing data in Evt memory
* Once used the Evt memory MUST be deallocated by the caller!
*
* [IN]  EventPtr : pointer to the requested event in the acquisition buffer (MUST BE NULL)
* [OUT] Evt      : event structure with the requested event data
*                : return  0 = Success; 
******************************************************************************/
int32_t X742_DecodeEvent(char *evtPtr, void **Evt);

And this is the implementation:

int32_t X742_DecodeEvent(char *evtPtr, void **Evt) {
    CAEN_DGTZ_X742_EVENT_t *Event;
    uint32_t *buffer;
    char chanMask;
    uint32_t j,g,size;
    uint32_t *pbuffer;
    uint32_t eventSize;
    int evtSize,h;

    evtSize = *(long *)evtPtr & 0x0FFFFFFF;
    chanMask = *(long *)(evtPtr+4) & 0x0000000F;
    evtPtr += EVENT_HEADER_SIZE;
    buffer = (uint32_t *) evtPtr;
    pbuffer = (uint32_t *) evtPtr;
    eventSize = (evtSize * 4) - EVENT_HEADER_SIZE;
    if (eventSize == 0) return -1;
    Event = (CAEN_DGTZ_X742_EVENT_t *) malloc(sizeof(CAEN_DGTZ_X742_EVENT_t));
    if (Event == NULL) return -1;
    memset( Event, 0, sizeof(CAEN_DGTZ_X742_EVENT_t));
    for (g=0; g<X742_MAX_GROUPS; g++) {
        if ((chanMask >> g) & 0x1) {
        for (j=0; j<MAX_X742_CHANNEL_SIZE; j++) {
            Event->DataGroup[g].DataChannel[j]= malloc(X742_FIXED_SIZE * sizeof (float));
            if (Event->DataGroup[g].DataChannel[j] == NULL) {
                for (h=j-1;h>-1;h++) free(Event->DataGroup[g].DataChannel[h]);
                return -1;
            }
        }
        size=V1742UnpackEventGroup(g,pbuffer,&(Event->DataGroup[g]));
        pbuffer+=size;
        Event->GrPresent[g] = 1;    
        } 
        else {
            Event->GrPresent[g] = 0;
            for (j=0; j<MAX_X742_CHANNEL_SIZE; j++) {
                Event->DataGroup[g].DataChannel[j] = NULL;
            }
        }
    }
    *Evt = Event;
    return 0;
}

I use this by:

CAEN_DGTZ_X742_EVENT_t* Evt = NULL; // Creating my event pointer
//Doing some config of the device
X742_DecodeEvent(evtptr, &Evt); //Decode the event data for me to read (Throws error)

Hope this gives some context.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Tsangares
  • 780
  • 1
  • 9
  • 27
  • 4
    void ** v = (void**)&c; – Michael Chourdakis Jul 09 '19 at 21:36
  • @MichaelChourdakis Success, do you mind writing an answer explaining why this casting works? – Tsangares Jul 09 '19 at 21:38
  • 7
    @MichaelChourdakis That's really terrible advice. All you've done is suppress the compiler warning. – NathanOliver Jul 09 '19 at 21:40
  • 3
    Why is this question even tagged c++. Everything screams c. – Tarick Welling Jul 09 '19 at 21:49
  • @TarickWelling Sorry I'm not to strong on what the difference nuances are. Ill change the tag. – Tsangares Jul 09 '19 at 21:51
  • @NathanOliver yes. What else do you expect with that sort of question? – Michael Chourdakis Jul 09 '19 at 21:51
  • @Tsangares How do you compile the code? If you use a C compiler, use C tag. If you compile as C++, tag it as C++. It's customary to not tag both unless necessary. – HolyBlackCat Jul 09 '19 at 21:53
  • @HolyBlackCat I am compiling as C++ but the libraries I included are C. I thought I was allowed to do that. Maybe if I switch that it will suppress this warning. – Tsangares Jul 09 '19 at 21:54
  • @TarickWelling Out of curiosity how did you tell this was C and not C++? – Tsangares Jul 09 '19 at 21:56
  • @Tsangares In general, C is not compilable as C++. There exists a subset of C that is valid C++, and if the library is purposefully written in that subset, then you can do that. But if the library happens to be incidentally valid C++, then a later version may stop being valid. – eerorika Jul 09 '19 at 22:18
  • @Tsangares As for how to recognise that this is probably not C++; a few examples: `malloc` is typically not used in C++ (except when interfacing with C libraries that require it). `memset` is even less used in C++, as value initialisation is simpler and more robust. – eerorika Jul 09 '19 at 22:43
  • Any pointer (except a pointer to a member, which is something different, despite the name) can be implicitly converted to a `void *`. That rule is not recursive, so `int **` cannot be converted to a `void **`. – Peter Jul 10 '19 at 01:11
  • The omission of RAII, usage of macros, malloc/memset, raw buffer usage, type juggling with pointers. This code is idiomatic for C not for C++. – Tarick Welling Jul 10 '19 at 08:18

3 Answers3

5

void** means a pointer to a void* object. But there is no void* object in that code to point at! void** does NOT mean "a pointer to any kind of pointer", so please avoid using it as such. If you have a pointer to something which might be an int*, might be a double*, or etc., void* is a better type than void**. Even better would be a template or std::variant or std::any.

But if you have to use a library that is using void** to mean "a pointer to a pointer to a type unknown at compile time" or something like that, you might need to create a void* pointer to work with, or might need to add in casts to get around the fact that the compiler doesn't like this conversion (for good reason). The problem is, there are at least two reasonable ways to do this! (They will end up doing exactly the same thing on many common computer architectures, but this is not guaranteed.)

// LibraryFunc1 takes a void** argument that somehow means an int* pointer.
// But which call is correct?
int* data_in = generate_data();
LibraryFunc1(reinterpret_cast<void**>(&data_in)); // ?
void* p1 = data_in;
LibraryFunc1(&p1); // ?

// LibraryFunc2 returns a void** argument that somehow means an int* pointer.
void** p2 = LibraryFunc2();
int* data_out_1 = static_cast<int*>(*p2); // ?
int* data_out_2 = *reinterpret_cast<int**>(p2); // ?

Based on the function definition shown, the safe usage is unfortunately:

void* tmpEvt;
X742_DecodeEvent(evtptr, &tmpEvt);
auto* Evt = static_cast<CAEN_DGTZ_X742_EVENT_t*>(tmpEvt);

since the library function assumes at *Evt = Event; that *Evt is actually a void* object it can modify. It may usually work to do the simpler thing instead:

CAEN_DGTZ_X742_EVENT_t* Evt = NULL;
X742_DecodeEvent(evtptr, reinterpret_cast<void**>(&Evt));

but this is undefined behavior by the C++ Standard, and might do the wrong thing on some architectures.

You could make the correct way easier by wrapping it in a function:

inline CAEN_DGTZ_X742_EVENT_t* Get_X742_DecodeEvent(char* evtPtr)
{
    void* tmpEvt;
    X742_DecodeEvent(evtPtr, &tmpEvt);
    return static_cast<CAEN_DGTZ_X742_EVENT_t*>(tmpEvt);
}
aschepler
  • 70,891
  • 9
  • 107
  • 161
4

What am I doing wrong with casting a variable to void**?

There is no meaningful way to convert int** to void**, so what you're trying to do is wrong.

What you may do is

int n = 11;
void* c = &n;
void** v = &c;

But without a complete example, it is not possible to say whether applies to your problem.

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

That's simply how the language works.

void * pointers get special treatment: a pointer to an arbitrary type can be converted to a pointer to void (as long as doing so doesn't remove cv-qualifiers from the pointer).

void ** gets none of that special treatment. It's just a regular pointer type, like int **.


int32_t X742_DecodeEvent(char *evtPtr, void **Evt)

Since you want to pass CAEN_DGTZ_X742_EVENT_t ** to your function, you should change the parameter type accordingly: CAEN_DGTZ_X742_EVENT_t **Evt.


In comments you were suggested to use void ** v = (void**)&c;.

While you could probably make it work in practice, strictly speaking any access to *v would violate strict aliasing and cause undefined behavior. I wouldn't use that solution.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207