简体   繁体   中英

Why can I cast int and BOOL to void*, but not float?

void* is a useful feature of C and derivative languages. For example, it's possible to use void* to store objective-C object pointers in a C++ class.

I was working on a type conversion framework recently and due to time constraints was a little lazy - so I used void* ... That's how this question came up:

Why can I typecast int to void*, but not float to void*?

BOOL is not a C++ type. It's probably typedef or defined somewhere, and in these cases, it would be the same as int. Windows, for example, has this in Windef.h:

    typedef int                 BOOL;

so your question reduces to, why can you typecast int to void*, but not float to void*?

int to void* is ok but generally not recommended (and some compilers will warn about it) because they are inherently the same in representation. A pointer is basically an integer that points to an address in memory.

float to void* is not ok because the interpretation of the float value and the actual bits representing it are different. For example, if you do:

   float x = 1.0;

what it does is it sets the 32 bit memory to 00 00 80 3f (the actual representation of the float value 1.0 in IEEE single precision). When you cast a float to a void*, the interpretation is ambiguous. Do you mean the pointer that points to location 1 in memory? or do you mean the pointer that points to location 3f800000 (assuming little endian) in memory?

Of course, if you are sure which of the two cases you want, there is always a way to get around the problem. For example:

  void* u = (void*)((int)x);        // first case
  void* u = (void*)(((unsigned short*)(&x))[0] | (((unsigned int)((unsigned short*)(&x))[1]) << 16)); // second case

Pointers are usually represented internally by the machine as integers. C allows you to cast back and forth between pointer type and integer type. (A pointer value may be converted to an integer large enough to hold it, and back.)

Using void* to hold integer values in unconventional. It's not guaranteed by the language to work, but if you want to be sloppy and constrain yourself to Intel and other commonplace platforms, it will basically scrape by.

Effectively what you're doing is using void* as a generic container of however many bytes are used by the machine for pointers. This differs between 32-bit and 64-bit machines. So converting long long to void* would lose bits on a 32-bit platform.

As for floating-point numbers, the intention of (void*) 10.5f is ambiguous. Do you want to round 10.5 to an integer, then convert that to a nonsense pointer? No, you want the bit-pattern used by the FPU to be placed into a nonsense pointer. This can be accomplished by assigning float f = 10.5f; void *vp = * (uint32_t*) &f; float f = 10.5f; void *vp = * (uint32_t*) &f; , but be warned that this is just nonsense: pointers aren't generic storage for bits.

The best generic storage for bits is char arrays, by the way. The language standards guarantee that memory can be manipulated through char* . But you have to mind data alignment requirements.

Standard says that 752 An integer may be converted to any pointer type . Doesn't say anything about pointer-float conversion.

The question is based on a false premise, namely that void * is somehow a "generic" or "catch-all" type in C or C++. It is not. It is a generic object pointer type, meaning that it can safely store pointers to any type of data, but it cannot itself contain any type of data.

You could use a void * pointer to generically manipulate data of any type by allocating sufficient memory to hold an object of any given type, then using a void * pointer to point to it. In some cases you could also use a union, which is of course designed to be able to contain objects of multiple types.

Now, because pointers can be thought of as integers (and indeed, on conventionally-addressed architectures, typically are integers) it is possible and in some circles fashionable to stuff an integer into a pointer. Some library API's have even documented and supported this usage — one notable example was X Windows.

Conversions between pointers and integers are implementation-defined, and these days typically draw warnings, and so typically require an explicit cast, not so much to force the conversion as simply to silence the warning. For example, both the code fragments below print 77 , but the first one probably draws compiler warnings.

/* fragment 1: */
int i = 77;
void *p = i;
int j = p;
printf("%d\n", j);

/* fragment 2: */
int i = 77;
void *p = (void *)(uintptr_t)i;
int j = (int)p;
printf("%d\n", j);

In both cases, we are not really using the void * pointer p as a pointer at all: we are merely using it as a vessel for some bits. This relies on the fact that on a conventionally-addressed architecture, the implementation-defined behavior of a pointer/integer conversion is the obvious one, which to an assembly-language programmer or an old-school C programmer doesn't seem like a "conversion" at all. And if you can stuff an int into a pointer, it's not surprising if you can stuff in other integral types, like bool , as well.

But what about trying to stuff a floating-point value into a pointer? That's considerably more problematic. Stuffing an integer value into a pointer, though implementation-defined, makes perfect sense if you're doing bare-metal programming: you're taking the numeric value of the integer, and using it as a memory address. But what would it mean to try to stuff a floating-point value into a pointer?

It's so meaningless that the C Standard doesn't even label it "undefined". It's so meaningless that a typical compiler won't even attempt it. And if you think about it, it's not even obvious what it should do. Would you want to use the numeric value, or the bit pattern, as the thing to try to stuff into the pointer? Stuffing in the numeric value is closer to how floating-point-to-integer conversions work, but you'd lose your fractional part. Using the bit pattern is what you'd probably want, but accessing the bit pattern of a floating-point value is never something that C makes easy, as generations of programmers who have attempted things like

uint32_t hexval = (uint32_t)3.0;

have discovered.

Nevertheless, if you were bound and determined to store a floating-point value in a void * pointer, you could probably accomplish it, using sufficiently brute-force casts, although the results are probably both undefined and machine-dependent. (That is, I think there's a strict aliasing violation here, and if pointers are bigger than floats, as of course they are on a 64-bit architecture, I think this will probably only work if the architecture is little-endian.)

float f = 77.75;
void *p = (void *)(uintptr_t)*(uint32_t *)&f;
float f2 = *(float *)&p;
printf("%f\n", f2);

dmr help me, this actually does print 77.75 on my machine.

Considering any of you want you transfer float value as void * , there is a workaround using type punning.

Here is an example;

    struct mfloat {
        union {
            float fvalue;
            int ivalue;
        };
    };

    void print_float(void *data)
    {
        struct mfloat mf;

        mf.ivalue = (int)data;
        printf("%.2f\n", mf.fvalue);
    }


    struct mfloat mf;
    mf.fvalue = 1.99f;
    print_float((void *)(mf.ivalue));

we have used union to cast our float value(fvalue) as an integer(ivalue) to void*, and vice versa

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM