Search code examples
cundefined-behaviorvoid-pointersstrict-aliasing

Methods to convert `void *` function parmeter inconsistant from type to type


Note: This question attempts to improve what I attempted to ask here, but fell short.
Also, I have seen this, and this. They discuss similar concepts, but do not answer these questions.

My environment is Windows 10, and for testing I used two compilers, CLANG and GCC.

I am passing variables via a void * function argument, and need to convert them. I would like to get some feedback on inconsistencies I am seeing between methods for different types.

The following is a stripped-down depiction of a test function that accommodates multiple input types using a void * parameter, and an enumerated value parameter to indicate the type being passed in.:

void func(void *a, int type)
{
    switch(type) {
        case CHAR://char
            char cVar1    = (char)a;      //compiles with no warnings/errors, seems to work
            char cVar2    = *(char *)a;   //compiles with no warnings/errors, seems to work
            break;
        case INT://int
            int iVar1     = (int)a;       //compiles with no warnings/errors, seems to work
            int iVar2     = *(int *)a;    //compiles with no warnings/errors, seems to work
            break;
        case FLT://float
            float fVar1   = (float)a;      //compile error:  (a1)(b1)
            float fVar2   = *(float *)a;   //requires this method
         case DBL://double
            double dVar1  = (double)a;     //compile error: (a1)(b1)(b2)
            double dVar2  = *(double *)a;//this appears to be correct approach
            break;
    };
}  

Calling method:

int main(void)
{

    char   c = 'P';
    int    d = 1024;
    float  e = 14.5;
    double f = 0.0000012341;
    double g = 0.0001234567;

    void *pG = &g;

    func(&c, CHAR);//CHAR defined in enumeration, typical
    func(&d, INT);
    func(&e, FLT);
    func(&f, DBL);
    func(pG, DBL);

    return 0;
}

Exact error text relating to flags in comments above follows:

CLANG - version 3.3

  • (a1) - ...error: pointer cannot be cast to type 'float'

gcc - (tdm-1) 5.1.0

  • (b1) - ...error: pointer value used where a floating point value was expected
  • (b2) - ...error: pointer cannot be cast to type 'double'

For reference in discussion below

  • method 1 == type var = (type)val;
  • method 2 == type var = *(type *)val;

My results indicate that converting float & double require method 2.
But for char & int method 2 seems to be optional, i.e. method 1 compiles fine, and seems to work consistently.

questions:

  • It would seem that recovering a value from a void * function argument should always require method 2, so why does method 1 (seem to) work with char and int types? Is this undefined behavior?

  • If method 1 works for char and int, why does it not also work with at least the float type? It's not because their sizes are different, i.e.: sizeof(float) == sizeof(int) == sizeof(int *) == sizeof(float *). Is it because of a strict aliasing violation?


Solution

  • The C standard explicitly allows conversions between pointers and integer types. This is spelled out in section 6.3.2.3 regarding pointer conversions:

    5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

    6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

    Assuming you cast an integer type to void * when passing it to the function then cast it back to the proper integer type, this can be done provided the implementation allows it. GCC in particular will allow this assuming the integer type in question is at least as big as a void *.

    This is why the conversion will work for the char and int cases, however you would need to pass in the values (casted to void *) instead of the addresses.

    So for example if you called the function like this:

    func4((void *)123, INT);
    

    Then the function can do this:

    int val = (int)a;
    

    And val would contain the value 123. But if you called it like this:

    int x = 123;
    func4(&x, INT);
    

    Then val in the function would contain the address of x in main converted to an integer value.

    Casting between a pointer type and a floating point type is explicitly disallowed as per section 6.5.4p4 regarding the cast operator:

    A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type.

    Of course the safest way to pass values via a void * is to store the value in a variable of the appropriate type, pass its address, then cast the void * in the function back to the proper pointer type. This is guaranteed to work.