cswitch-statementvoid-pointers

Runtime cast of void pointer


I'm writing a C program that applies a different type of quantization to an array depending on an environmental variable.

The problem is that, I need to use the same function void foo(void* ptr, quant_t type, ...) to do things on ptr, but I need to cast it beforehand to the correct type.

(quant_t is an enum type object)

I've tried to do

void foo(void* ptr, quant_t type, ...){
switch(type){
case UNIF:{
struct unif* my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
struct kmeans* my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

But it does not work because the declaration of my_ptr is inside the scope of a switch case.

So I've tried to do something like this:

void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

But it still does not work.


Solution

  • For ->a to work, the compiler must know the location (among other things) of the field relative to the pointer. This offset must be constant in C.

    We can make this work by making your two types compatible with a third, and using that third type (Base).

    typedef struct {
       A a;
       B b;
    } Base;
    
    typedef struct {
       Base base;
       …
    } Unif;
    
    typedef struct {
       Base base;
       …
    } Kmeans;
    
    // `base` doesn't need to be the first field of `Unif` and `Kmeans`
    // (unless you want to recuperate the orig ptr from a `Base *` at some point).
    
    void foo( Base *ptr, … ) {
       ptr->a = bar( … );
       ptr->b = baz( … );
    }
    
    Unif *unif = …;
    foo( &unif->base, … );
    
    Kmeans *kmeans = …;
    foo( &kmeans->base, … );
    

    It's less safe, but we could also have called it as follows:

    // `base` must be the first field of `Unif` and `Kmeans`.
    
    void foo( Base *ptr, … ) {
       ptr->a = bar( … );
       ptr->b = baz( … );
    }
    
    Unif *unif = …;
    foo( (Base *)unif, … );
    
    Kmeans *kmeans = …;
    foo( (Base *)kmeans, … );
    

    Which means we can make a few changes to reduce the code needed in the caller a little.

    // `base` must be the first field of `Unif` and `Kmeans`.
    
    void foo( void *ptr_, … ) {
       Base *ptr = ptr_;
       ptr->a = bar( … );
       ptr->b = baz( … );
    }
    
    Unif *unif = …;
    foo( unif, … );
    
    Kmeans *kmeans = …;
    foo( kmeans, … );
    

    The last two are not as safe as the original as they defeat type checks through explicit and implicit casts.