8

In C and/or C++: is it safe to cast an int to void pointer and back to int again?

Based on the question "C++: Is it safe to cast pointer to int and later back to pointer again?".

Community
  • 1
  • 1
Lesmana
  • 25,663
  • 9
  • 82
  • 87
  • 7
    I do wish people would pick ONE language to ask questions like this. You don't see people ask, "How do you check a reference for null in Java or C++?" Why do people insist on asking such questions about C and C++? They both use { so the must be the same? – Edward Strange Aug 25 '10 at 16:30
  • 1
    Well.... in this case, C and C++ ARE the same in implementations I've worked with with regard to this, but a peeve of my own all the same. – Matt Joiner Aug 25 '10 at 16:32
  • I don't think they are the same though. Do you need to consider pointer size in C? In C++ you've got all sorts of different types of pointers that can be different sizes, with void* being big enough to hold any. Thus a cast of int to void* is less likely to loose information than a cast of int to say int* or anything else. I don't remember that being the case in C but admittedly haven't used it since I was a novice. – Edward Strange Aug 25 '10 at 16:38
  • @Noah Roberts: You're wrong. In C++, you can have pointer-to-member pointers that aren't guaranteed to fit in a void*. That's ignoring the actual new custom pointer types, which the Standard can't guarantee anything about. – Puppy Aug 25 '10 at 17:28
  • Yes, I misspoke. In C++ any T* can fit into a void* while not all T1* can fit into T2* or visa-versa. I don't remember C having this. – Edward Strange Aug 25 '10 at 17:43
  • @Noah Roberts: C is the same in that sense - although it does give some other guarantees (eg. `char *` has same size and alignment as `void *`; all `struct *` have the same size and alignment) – caf Aug 26 '10 at 05:26
  • @Cra: C++ incorporates many language rules from C. There is a *formal* relationship between C and C++. It's understandable, that users ask about C and C++. They are asking a genuine question. They don't know, whether C and C++ deviate on this particular aspect. You don't see users asking about Java and C++, because there is no formal relationship between them. – IInspectable Sep 01 '18 at 07:26

8 Answers8

13

In most modern-day commonplace machines, probably.

However, I'd bet that there is some obscure compiler or configuration (say, a 16-bit addressed machine that uses 32-bit integer arithmetic) where that is not the case.

A uintptr_t is guaranteed to hold both, though, so use that type if you want to.

Steven Schlansker
  • 37,580
  • 14
  • 81
  • 100
5

Here is an example where converting a pointer to an integer may not result in the same pointer when converting the integer to a pointer.

Given an architecture which has 24 bit addresses and uses two 16-bit quantities to describe the location. Let one quantity be the SEGMENT and the other OFFSET. A location is designated by the notation SEGMENT:OFFSET.

The actual 24-bit (Physical) address is calculated by:

address = segment * 16 + offset.

Using this notation, there can be more than one SEGMENT:OFFSET pair that describe the same physical address.

When converting to an integer, a 32-bit (unsigned) quantity is used (to simplify internal calculations in the processor). The problem is how to convert the physical address into the same SEGMENT::OFFSET that was used in the creation of the physical address.

A generic equation for converting integer to pointer is:

offset = address & 0xFFFF; // Mask off high order bits, keep lower 16.
segment = address >> 16;   // Shift address right 16 bits, zero fill.

Although the physical address of this new segment and offset is equal to the physical address of the original SEGMENT:OFFSET, the segments and offsets are not guaranteed to be the same.

To optimize code, there are processor instructions that use relative addressing in a segment. These instructions may get messed up when the SEGMENT value changes due to conversion from a physical address.

In this scenario, converting from a pointer to an integer is possible. HOWEVER, converting from the integer to the pointer IS STRONGLY DISCOURAGED. Hard to debug errors could occur during run-time.

Bonus question: Can you name the actual architecture?

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
3

Why would you want to do this?

Reply for C, I don't know enough about C++ for that: No, behavior is not defined to cast an int to void*. First of all you should always use uintptr_t if you have it for such a thing. Using int is an abuse.

Then, C does not guarantee anything if your uintptr_t doesn't come from a valid address. It only guarantees the other way round. Don't do it.

Edit: Here is the relevant part of the C99 standard. As you can see all alarms can go off...

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

The last is particularly embarrassing since this means that the pointer value that is such obtained can not be used anymore, until it is overwritten:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. ... Such a representation is called a trap representation.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 1
    All that says is "an int can be converted to a pointer type, but don't try to dereference it". It says nothing about the safety of later converting it back into an integer. (Which as stated, is probably safe on most modern platforms, but you should still use uintptr_t.) –  Aug 25 '10 at 21:02
  • @Joe: yes it does. In particular it says that the result may be a trap representation, or in other words an invalid value for the type. Trying to access a variable (of any type) that contains a trap representation is undefined behavior. I'll edit this in, too. – Jens Gustedt Aug 25 '10 at 21:17
  • 1
    As to "why would one want to do that?": For example if you start a thread with pthread_create() and want/need to pass just one int argument to the thread then it is tempting to cast that int as void* pointer and pass it to to the start_routine via the arg parameter. But alas it is not safe though would work in the most common architectures. – nyholku Dec 12 '19 at 15:03
  • 1
    I'm not an expert, but the standard text says "... and is read by an LVALUE" expression. So what does that actually mean? If we have some void* p, then and do (int)p, then that is a RVALUE and thus the trap representation clause does not apply? – nyholku Dec 12 '19 at 15:11
2

No. A void pointer is no different from any other pointer with respect to size. Hence it will run into exactly the same types of issues as other pointer types.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Actually, it is (in C++). It's guaranteed to be large enough to store any other pointer. Any cast from T* to void* and back to T* is guaranteed to result in the same T* you initially casted. – Edward Strange Aug 25 '10 at 16:28
  • 2
    @Noah, take a look at the linked question. The OP is asking if it's safe to cast `int` to `void*` and back again. I'm stating that `void*` is not special and it will have exactly the same problems as casting `int` to `T*`. – JaredPar Aug 25 '10 at 16:30
  • Not my fault you didn't say what you meant. – Edward Strange Aug 25 '10 at 16:33
  • 1
    I think I get your point, but I take issue with "A void pointer is no different from any other pointer with respect to size". `void*` *is* special. – jamesdlin Aug 25 '10 at 17:19
2

It's implementation defined just like the last question and for the same reason. It's less likely to result in misbehavior but it's still implementation defined.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
2

No. There might be certain circumstances where it appears to work for a certain compiler&settings, and then two years later you spend weeks debugging that something changed and the conversion no longer works as expected.

If you just design your code in a way that doesn't need this sort of behavior (best case avoids use of such conversion at all, worst case use char[]) then you won't have to worry about obscure bugs in the future.

Mark B
  • 95,107
  • 10
  • 109
  • 188
0

Not necessarily. Depends on the size of a pointer. Why would you want to do this?

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 4
    One reason I've seen - an API lets you pass it a void* generic value that it will pass back to you at some later time. It's nice to have the flexibility of using an int instead of a pointer. A better API will use a type guaranteed to handle both, but that wasn't common in older code. – Mark Ransom Aug 25 '10 at 17:30
  • @Mark: That was perhaps true in the old days, but nowadays a `uintptr_t` really suits better for such a feature. If the platform doesn't have `uintptr_t` (I don't know of any) then probably such unifying type simply doesn't exist, and one should be really careful, anyhow. – Jens Gustedt Aug 25 '10 at 21:28
0

If the range of your integers is fairly small, you could always do something like:

static const char dummy[MAXVAL];

and then use dummy+i as a way of encoding i as a pointer. This is 100% portable. If you only care that it's 99% portable, you could use (char *)0 + i.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711