Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Definedness of pointer-integer casts

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);
}

  • Which of the functions invoke undefined (implementation-defined) behavior?
  • Does anything change if using void* instead of int*? How about any other data type as the target of the pointer?
  • Does anything change if I use explicit casts from from int* to intptr_t and back? (Asking since GCC warns about the casts.)
  • Which asserts are guaranteed never to trigger?
like image 447
rtoijala Avatar asked Jul 24 '20 16:07

rtoijala


1 Answers

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 to void, 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.

like image 176
Artyer Avatar answered Sep 27 '22 17:09

Artyer