15

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* ?

Joe
  • 7,378
  • 4
  • 37
  • 54
Jasper Blues
  • 28,258
  • 22
  • 102
  • 185
  • Objective-C references are type `id`. See http://stackoverflow.com/questions/1304176/objective-c-difference-between-id-and-void – Potatoswatter Jan 11 '13 at 07:03

5 Answers5

21

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
thang
  • 3,466
  • 1
  • 19
  • 31
  • 2
    `BOOL` is a typedef'd `signed char` in ObjC. – jscs Jan 11 '13 at 07:28
  • 2
    +1 for *"[...] 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?"* – Nawaz Jan 11 '13 at 08:05
  • 2
    Nitpick: `0000803f` is little-endian byte order; `3f800000` is what it looks like as an `int` regardless of endianness. And `int` to `void*` is not strictly OK; see my answer. – Potatoswatter Jan 11 '13 at 08:14
  • Wait, what does `unsigned short` have to do with anything? Your second case doesn't appear to work. See my answer for the "correct" implementation. – Potatoswatter Jan 11 '13 at 08:18
  • the second part of the expression got cut out. this is why it looks like i weirdly used [0] instead of just (asterisk). i fixed it. what i wanted to do was to piece together the data from two 16-bit numbers. the only way to guarantee data size is to use unsigned short (or unsigned char). long and int can change in size based on the architecture. your example wouldn't work in 64-bit architecture. in fact, it can crash because you're dereferencing a pointer to a 32-bit piece of memory as a void* pointer, but void* is 64-bit. – thang Jan 11 '13 at 08:40
  • 1
    @thang The new expression is also wrong; `|` means OR not catenation. Anyway 16-bit numbers have no relation to anything here. Indeed you are correct about my code potentially crashing, but it's the most common way to do a wrong thing and it will work on any platform with Objective-C. Hmm, I can fix it to support 64-bit better. Anyway, the best way is to call `memcpy`, not improvise an expression. And once you get that far, might as well declare a `char[4]` or `char[8]` not a `void*`. – Potatoswatter Jan 11 '13 at 08:51
  • | is used as OR in the code. That's why there is a << 16. To concatenate two 16 bit numbers a and b, what you can do is a|(((unsigned int)b)<<16) assuming size of int is > 16. This is standard practice. the << shifts all the bits of b into the top 16-bits. have you looked at an implementation for memcpy? also, when you memcpy, it doesn't zero out the top part of the void* (for 64-bit architecture). ok, so you can just as well do memset then memcpy, but have a look at the implementation for those functions. they're messy. in effect, what i am doing is a really fast memcpy. – thang Jan 11 '13 at 08:55
  • @thang ah, didn't scroll over to the shift. Still, there's no reason to intoduce a 16-bit anything here. Just cast to a 32-bit `uint32_t` instead. Ah, now I see that you're assuming `short` to be exactly 16 bits. That's not particularly valid, although it is likely. – Potatoswatter Jan 11 '13 at 08:58
  • uint32_t is not standard c++ type. you are right that by standard, only char is guaranteed to be 1 byte, but in practice short is typically pegged to 16-bits. int and long can fluctuate a lot with platforms. the problem with using char is that reading out 1 byte at a time may cause performance (or other) issues on some platforms (especially embedded). – thang Jan 11 '13 at 09:02
  • @thang `std::uint32_t` has now been standardized; it's been around for ages anyway (without `std::` qualification, obviously) and you will not find a platform without it. Writing correct embedded code without the `int*_t` types is just masochism. If you do find such a platform, then the compiler will complain and you will merely have to define it yourself. Assuming `short` is 16 bits on the other hand is flouting the standards, and will silently fail. – Potatoswatter Jan 11 '13 at 09:04
  • @thang: Wouldn't or solution depend on the endianess of the code? You assume big endian architecture but we don't know if it is the case. – Maciej Piechotka Jan 11 '13 at 09:56
  • `void* u = (void*)(((unsigned short*)(&x))[0] | (((unsigned int)((unsigned short*)(&x))[1]) << 16));` is a strict-aliasing violation and undefined behavior. Using `[[un[signed] char` instead of `unsigned short` would work. It also assumes an `unsigned short` is 16 bits. – Andrew Henle Jan 28 '23 at 14:13
17

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;, 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.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Re: 2nd paragraph: You can do this more portably with `uintptr_t`. – asveikau Jan 11 '13 at 07:20
  • @asveikau `uintptr_t` is big enough to hold a pointer value. He's trying to use `void*` to hold an integer value, which is the other way around. According to the language specs, that's allowed to crash. C++11 even adds provisions for the machine checking that pointer values were computed properly, as a means of increased reliability. – Potatoswatter Jan 11 '13 at 07:24
  • Ah, OK. You're right. Should have paid more attention before commenting. – asveikau Jan 11 '13 at 07:25
  • 1
    personally I find `void*` has no place in a proper C++ program but that is just me. – AndersK Jan 11 '13 at 08:09
  • @claptrap Many including me feel that way. It can be used for Pimpl but more appropriate is to define a named, incomplete type using `stuct`. – Potatoswatter Jan 11 '13 at 08:16
  • Another difference with `uintptr_t` is that it only exists on systems where the conversion would work. Not all systems have integers the size of pointers (or vice versa). – Bo Persson Jan 11 '13 at 08:26
  • Your solution breaks strict aliasing rule (also called "rule which contradicts how you think about C programs but it have too many useful applications to be simply ignored"). Legal solution would be `void *vp = NULL; memcpy(&vp, &f, std::min(sizeof(vp), sizeof(f)));`. At least gcc is able to optimize out such memcpy. – Maciej Piechotka Jan 11 '13 at 09:49
  • @MaciejPiechotka Yes, I mentioned the only valid way is through `char*` pointers. Gah, this is the duplicate question from hell. – Potatoswatter Jan 11 '13 at 13:49
  • @Potatoswatter: Sorry. The second-to-last paragraph implies to me that 'after' `void *vp = * (uint32_t**) &f;` `vp` will have the same bitpattern as `f` - which is not correct as compiler is free to optimize it and `vp` will have garbage (i.e. even bigger garbage then expected). So this line is not accomplishing what it looks like it accomplishing (get the `f` bitpattern). – Maciej Piechotka Jan 11 '13 at 13:55
  • @MaciejPiechotka Sorry, that was a typo. It used to say `* (void**) &f` but I changed `void` to `uint32_t` to add "64-bit support" (hah!) without removing a `*`. Thanks for pointing it out, but this answer is still only meeting a bar that the question managed to set very low. – Potatoswatter Jan 11 '13 at 14:03
  • @Potatoswatter: I believe it still doesn't help. The type punning in general is not allowed except `char`. Hence It might read contents of `f` but standard doesn't guarantee it. Take http://pastebin.com/DJhaTBaa - you will get different results in gcc with `-O0` and `-O3`. – Maciej Piechotka Jan 11 '13 at 14:22
  • @MaciejPiechotka You're preaching to the choir. Try explaining it to the OP. Write an answer and I'll upvote, but I don't think he's listening. – Potatoswatter Jan 11 '13 at 14:23
  • @Potatoswatter I was more discussing that your answer implies that this is legal way of reading bit pattern while it is not. (Who's 'OP'? There is no one with such nick on this question so I presume it is some sort of acronym). – Maciej Piechotka Jan 11 '13 at 14:26
  • @MaciejPiechotka OP = original poster, on this site the question-asker. Enough breath has been wasted on this already; please write your own answer if you want, or find one which discusses aliasing and link here in comments for the benefit of those who don't know. – Potatoswatter Jan 11 '13 at 16:03
3

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

kerim
  • 2,412
  • 18
  • 16
1

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

Gurhan Polat
  • 696
  • 5
  • 12
1

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.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • *I think there's a strict aliasing violation here* There's no thinking. `*(uint32_t *)&f` is definitely a strict-aliasing violation. But this does appear to be the only answer that notes that. – Andrew Henle Jan 28 '23 at 14:15