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
  • 6,884
  • 4
  • 36
  • 50
Jasper Blues
  • 27,820
  • 20
  • 98
  • 179
  • 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

3 Answers3

20

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,320
  • 17
  • 30
  • 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
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
  • 131,100
  • 23
  • 249
  • 407
  • 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
1

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

kerim
  • 2,244
  • 17
  • 16