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

人走茶凉 提交于 2020-02-22 19:21:55

问题


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?


回答1:


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.




回答2:


At your call sites, you are passing the address of each variable.

func4(&c, CHAR);
func4(&d, INT);
func4(&e, FLT);
func4(&f, DBL);
func4(pG, DBL);

(This is the right thing to do.) Therefore, inside func4, you must use what you are describing as "method 2":

T var1    = (T)a;    // WRONG, for any scalar type T
T var2    = *(T *)a; // CORRECT, for any scalar type T

You only got compile-time errors for floating-point types T because the C standard explicitly allows casts from pointer to integer types. But those casts produce a value that has some [implementation-defined] relation to the address of the variable supplied as an argument, not to its value. For instance,

#include <stdio.h>
int main(void)
{
    char c = 'P';
    printf("%d %d\n", c, (char)&c);
    return 0;
}

is a valid program that prints two numbers. The first number will be 80 unless you're running on an IBM mainframe. The second number is unpredictable. It could also be 80, but if it is, that's an accident, not something to rely on. It may not even be the same number each time you run the program.

I don't know what you mean by "[method 1] seems to work", but if you actually got the same value you passed in, it was purely by accident. Method 2 is what you should be doing.




回答3:


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?

Because C specifically allows conversions between integers and pointers. This is allowed since there can be a need to express absolute addresses as integers, particularly in hardware-related programming. The result may be fine or it may invoke undefined behavior, see details below.

When you need to convert between pointers and integers, you should however always use uintptr_t instead, for well-defined and portable conversions. This type wasn't part of C originally, which is why conversions to other integer types is still allowed.

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?

Because floating point types do not have a special case allowed conversion like integer types do. They rather have an explicit rule forbidding casts from pointers to floating point. Since it doesn't make any sense to do such conversions.

Strict aliasing only applies when you do a "lvalue access" of the value stored. You only do that for example here: *(double *)a. You access a the data through a type (double) compatible with the effective type of the object (also double), so this is fine.

(double *)a however, is never accessing the actual data, but just attempts to convert the pointer type to something else. So strict aliasing doesn't apply.

Generally, C allows a whole lot of wild pointer conversions, but you only get in trouble once you start to actually de-reference the data through an incorrect type. It is then you can run into problems with incompetible type, misalignment and strict aliasing.


Details:

  • char c = 'P'; ... char cVar1 = (char)a;.
    Conversion from pointer type to integer type. The result is undefined or implementation-defined 1). No lvalue access of the pointed-at data occurs, strict aliasing does not apply 2).
  • char c = 'P'; ... char cVar2 = *(char *)a;.
    Lvalue access of character through character pointer. Perfectly well-defined 3).
  • int d = 1024; ... int iVar1 = (int)a;.
    Conversion from pointer type to integer type. The result is undefined or implementation-defined 1). No lvalue access of the pointed-at data occurs, strict aliasing does not apply 2).

  • int d = 1024; ... int iVar2 = *(int *)a;
    Lvalue access of int through int pointer. Perfectly well-defined 3).

  • float e = 14.5; ... float fVar1 = (float)a;.
    Conversion from pointer type to float. Non-compatible type conversion, cast operator constraint violation 4).

  • float e = 14.5; ... float fVar2 = *(float *)a;.
    Lvalue access of float through float pointer. Perfectly well-defined 3).

  • double... same as float above.


1)C17 6.3.2.3/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.

2)C17 6.5 §6 and §7. See What is the strict aliasing rule?

3)C17 6.3.2.1 Lvalues, arrays, and function designators, and
C17 6.3.2.3/1:

A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

Also, type is perfectly fine to lvalue access through a (qualified) pointer to type, C17 6.5/7: "a type compatible with the effective type of the object".

4) Not one of the valid pointer conversions listed in C17 6.3.2.3. Constraint violation of C17 6.5.4/4:

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



来源:https://stackoverflow.com/questions/59754887/methods-to-convert-void-function-parmeter-inconsistant-from-type-to-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!