10

Section 7.18.1.4 of the C99 standard states:

The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:

uintptr_t

Does this mean that only void * types can be converted to uintptr_t and back without changing the value of the original pointer?

In particular, I would like to know if the following code is required to use uintptr_t:

int foo = 42;
void * bar = &foo;
uintptr_t baz = bar;
void * qux = baz;
int quux = *(int *)qux; /* quux == foo == 42 */

Or if this simpler version is guaranteed by the C99 standard to result in the same effect:

int foo = 42;
uintptr_t bar = &foo;
int baz = *(int *)bar; /* baz == foo == 42 */

Is a conversion to void * required before converting a pointer to uintptr_t and vice versa?

Vilhelm Gray
  • 11,516
  • 10
  • 61
  • 114
  • Yes, an intermediate cast to `void*` (or a qualified version of `void*`) is required. – Ian Abbott Jul 27 '17 at 13:32
  • @IanAbbott Do you happen to know the rationale for the standard to require this intermediate cast to `void *`? – Vilhelm Gray Jul 27 '17 at 13:34
  • 1
    @VilhelmGray While I’m not sure why the compiler isn’t required to insert the cast by itself (it knows the types, after all), the underlying issue is that pointers of different types are not required to work the same way—for example, two pointers to different fields of a union aren't guaranteed to have the same bits in memory. They aren't even guaranteed to be the same _size_. The motivation is that there are machines where the byte (unit of memory addressing) is so large (_e.g._ 36 bits) as to make an impractical C `char`, (...) – Alex Shpilkin May 24 '21 at 23:09
  • (...) and a common workaround is to split the byte into several `char`s and make `char *` (and thus `void *`) hold both the address of the byte and the index of the `char` inside the byte. On the other hand, `int *` and other known-byte-aligned pointers do not need the index; thus, differently-sized pointers. A related issue is why `p < q` isn't guaranteed to be equivalent to `(uintptr_t)p < (uintptr_t)q`: some of the implementations of this workaround end up with the index in the _high_ part of the integer. – Alex Shpilkin May 24 '21 at 23:16
  • That said, AFAIU modern DSPs that have this problem usually suck it up and use a 32-bit (or whatever) `char`, and I find the gratuitous, probably compatibility-motivated, _newly introduced_ C99 footgun the question is concerned with (that the compiler has every necessary piece of information to eliminate, and that probably makes a substantial portion of `uintptr_t`-using code incompatible with the very systems that it was intended to ensure compatibility with) _intensely annoying_. – Alex Shpilkin May 24 '21 at 23:38

5 Answers5

8

The distinction exists also because while any pointer to an object can be converted to void *, C doesn't require that function pointers can be converted to void * and back again!

As for pointers to objects of other types, the C standard says that any pointer can be converted to an integer, and an integer can be converted to any pointer, but such results are implementation-defined. Since the standard says that only void * is convertible back and forth, the safest bet is to cast the pointer to void * first, as it might be that pointers that have different representations when converted to uintptr_t will result in a different integer representation too, so it could be conceivable that:

int a;
uintptr_t up = (uintptr_t)&a;
void *p = (void *)up;
p == &a; // could be conceivably false, or it might be even that the value is indeterminate.
4

There are various places where the authors of the Standard didn't think it necessary to mandate that compilers behave sensibly, because they expected compilers to do so anyhow.

If an implementation defines uintptr_t and p is a void*, the only thing the Standard mandates about void *q = (void*)(uintptr_t)p; is that q==p will compare equal. The Standard does not guarantee that q will be usable for any particular purposes for which p would have been usable, or even for any purpose other than comparisons with other pointers.

From a practical perspective, there's no reason an implementation should require casting to/from void* on either side of conversions through uintptr_t, but the Standard would allow implementations to behave in arbitrary fashion if such extra casts were omitted. On the other hand, since the Standard would also allow implementations to behave in arbitrary fashion in nearly all practical situations involving uintptr_t, the only real question is whether one is willing to rely upon implementers not to behave in obtuse fashion. Such reliance may be reasonable with some commercial compilers, but not so much with some free ones.

supercat
  • 77,689
  • 9
  • 166
  • 211
2

Yes an intermediate conversion to void* is required for this to be portable. This is because pointer to integer conversion is "implementation defined", so your platform could do anything as long as they document it.

BTW, you are mixing up "cast" and "conversion" in your question. "Conversion" is the more general term, "cast" is "explicit conversion".

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
1

Yes, the cast to/from void * is required.

The wording of this passage is similar to the wording of the %p format specifier to printf which required a void * as a parameter.

From section 7.19.6.1:

p The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.

You can do the above with less intermediate variables, however:

int foo = 42;
uintptr_t baz = (void *)&foo;
int quux = *(int *)((void *)baz); /* quux == foo == 42 */
dbush
  • 205,898
  • 23
  • 218
  • 273
  • I think the rationale behind this is that the implementation doesn't know the type expected for `printf` since the caller sees the declaration of `printf` as having `...`. But here when casting the compiler knows that `void*` can be converted. So an implicit cast is possible. – Ajay Brahmakshatriya Jul 27 '17 at 13:37
  • @AjayBrahmakshatriya the problem is that yes, *any* pointer can be converted, but only `void *` <=> `(u)intptr_t` is guaranteed to work both ways... – Antti Haapala -- Слава Україні Apr 03 '18 at 08:17
  • @AnttiHaapala I see, I wasn't aware of that. I thought `uintptr_t` can be freely casted to any pointer type. Thanks – Ajay Brahmakshatriya Apr 03 '18 at 08:20
0

If we are talking about pointers in C we should think about how they are represented. Basically the size of the pointer value depends on architecture not the type on which it is pointing. Value of the void* and value of uintptr_t is the same but representation could differ. In case uintptr_t according to documentation

The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer: uintptr_t.

This mean that this type could be used to safely store value of pointer and could be equal,larger or even smaller then void* itself due implementation specific details.

So to sum up by using conversion to void* you ensures that the conversion to/from uintptr_t is guaranteed to be valid.

j2ko
  • 2,479
  • 1
  • 16
  • 29