Reputation: 1209
I am interested in the definedness (undefinedness, implementation-definedness) of casts from pointers to integers and various related operations. Mostly I am interested in C11, but answers for other standard versions (or even C++) are welcome.
For the purposes of this question, assume that the C implementation provides intptr_t
.
Consider the following functions:
#include <assert.h>
#include <stdint.h>
int x;
int y;
int z[2];
void f1(void) {
int *p = &x;
intptr_t i = p;
}
void f2(void) {
int *p = &x;
intptr_t i1 = p;
intptr_t i2 = p;
assert(i1 == i2);
}
void f3(void) {
int *p1 = &x;
int *p2 = &y;
intptr_t i1 = p1;
intptr_t i2 = p2;
assert(i1 != i2);
}
void f4(void) {
int *p1 = &x;
intptr_t i1 = p1;
int *p2 = i1;
intptr_t i2 = p2;
assert(i1 == i2);
}
void f5(void) {
int *p1 = &z[0];
int *p2 = &z[1];
intptr_t i1 = p1;
intptr_t i2 = p2;
assert(i1 < i2);
}
void*
instead of int*
? How about any other data type as the target of the pointer?int*
to intptr_t
and back? (Asking since GCC warns about the casts.)assert
s are guaranteed never to trigger?Upvotes: 5
Views: 372
Reputation: 40891
This is what the C11 standard has to say about intptr_t
:
7.20.1.4 Integer types capable of holding object pointers
The following type designates a signed integer type with the property that any valid pointer to
void
can be converted to this type, then converted back to pointer tovoid
, and the result will compare equal to the original pointer:intptr_t
And the same for uintptr_t
(other than signed -> unsigned).
Also from "6.5.4p3 Cast operators":
Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.
Where 6.5.16.1 doesn't mention assigning pointers to an integer type, and vice versa (other than a 0
constant). That means that you do need a cast when assigning, gcc just allows it as a compiler extension (And it doesn't compile at all with -pedantic-errors
)
As for the exact value that is returned in these conversions, this is what the standard has to say:
6.3.2.3 Pointers
p5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, [...]
p6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. [...]
The basic guarantee you have is:
int x;
(int*) (void*) (intptr_t) (void*) &x == &x;
/* But the void* casts can be implicit */
(int*) (intptr_t) &x == &x;
And it's not necessary to cast to the same integer. E.g., the following can be true:
int x;
(intptr_t) &x != (intptr_t) &x;
Adding casts where necessary, and turning your asserts into returns (since assert(false)
is undefined behaviour), none of your functions have undefined behaviour, but f2
, f4
and f5
can be false. f3
must be true, since the two integers must be different to convert to different pointers.
Upvotes: 3
Reputation: 241881
If intptr_t
exists, then it is capable of holding a void*
without data loss:
The following type designates a signed 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:
intptr_t
(§7.20.1.4p1)
However, if the pointer is not a pointer to void, all bets are off unless it is a null pointer:
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. Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type. (§6.3.2.3p5-6)
"Previously specified" are the conversions between void*
and integer types, and the conversion of a null pointer constant to an integer type.
So a strictly correct program would need to interpose void*
casts:
intptr_t i = (intptr_t)(void*)p;
T* p = (void*)i;
That's ok, because round-trip conversion between any object type and void*
is guaranteed to be lossless:
A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
(The explicit intptr_t
case in the first line is necessary because assignment does not implicitly cast from pointer to integer, although some compilers allow that as an extension. However, in C the assignment operations do implicitly convert between void*
and other pointer types (§6.5.16.1p1, fourth bullet point).)
Note that "object type" excludes function pointers.
Also, the fact that the sequence void*
→intptr_t
→void*
is results in a value equal to the original value does not imply that the sequence intptr_t
→void*
→intptr_t
has the same property. While the conversion between pointers and integers is "intended to be consistent with the addressing structure of the execution environment", that statement is in a footnote, so it is not normative. And anyway it is possible that the "addressing structure of the execution environment" permits multiple representations of the same address. (You don't have to look too far to find examples :-). )
Upvotes: 2