Is &array[i] always equivalent to (array + i)?

Viewed 244

Recently, I saw a C code like this:

#include <stdio.h>

int main(void) {
    int array[5] = {1, 2, 3, 4, 5};

    for (int* ptr = &array[0]; ptr != &array[5]; ptr++)
        printf("%d\n", *ptr);

    return 0;
}

Since operator [] is prioritized over operator & in C, I think &array[5] is equivalent to &(*(array + 5)), which causes undefined behavior (we are not allowed to dereference array + 5). That is why I suspect the code above is ill-formed. (By the way, I know that ptr != array + 5 is okay.)

I tested this code using GCC 11.1.0 and Clang 12.0.0 with -O0 -fsanitize=address,undefined compiler flags, but both compilers interpreted &array[5] as array + 5, and no unexpected behavior happened.

Is &array[i] always equivalent to array + i (even when array[i] is invalid)? Thank you in advance.

3 Answers

Firstly there is 6.5.2.1/2:

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))

Then it is defined in (6.5.3.2/3) , the unary & operator:

[...] Similarly, if the operand is the result of a [] operator, neither the & operator nor the unary * that is implied by the [] is evaluated and the result is as if the & operator were removed and the [] operator were changed to a + operator.

Which is explicitly saying that &x[y] means (x) + (y) exactly.

even when array[i] is invalid

Answer from sanitiser's point of view:

&array[i] and array+i always give the same pointer, but only &array[i] will raise a runtime error by address-sanitizer (at least in gcc). So, in this respect they are not equivalent.

Note that, if i=5 in your case, the address sanitizer will not give an error if the pointer is not dereferenced, so the code above will work (even if sanitizer is turned on). If i is bigger than 5, however, sanitizer immediately gives an error. Regarding the code above it is suggested to use pointer arithmetic (if you insist on using pointers):

for (int* ptr = array; ptr < array+5; ptr++)

Although the C Standard defines the behavior of the [] operator in terms of the pointer addition and dereference operators + and *, + and unary * operators, such that that array[i] means *(array+i), neither clang nor gcc actually processes it that way. The behaviors are identical in circumstances where both operators would yield defined behavior, but they interact differently with the compilers' interpretation of the "strict aliasing rule".

The Standard, for example, makes no general allowance for accessing union objects using member-type lvalues. Given a declaration like:

union blob { unsigned short hh[4]; unsigned ww[2]; } u;

it would be absurd to suggest that the only way of accessing those members would be by using character pointers or functions like memcpy, especially since the Standard explicitly allows type punning via union objects. On the other hand, if one tests in gcc the functions:

int test1(int i, int j)
{
    u.hh[i] = 1;
    u.ww[j] = 2;
    return u.hh[i];
}
int test2(int i, int j)
{
    *(u.hh+i) = 1;
    *(u.ww+j) = 2;
    return *(u.hh+i);
}

both compilers will generate code for test1 that accommodate the possibility of type punning, but will generate for test2 code that unconditionally returns 1. This is allowable because the Standard characterizes both forms as Undefined Behavior so as to allow implementations to--on a Quality of Implementation basis--support whichever form(s) their customers would find useful.

While I don't know about how the behaviors compare in the particular expression you list, the fact that they interpret [] differently from a combination of + and * means that the Standard's definition of the former in terms of the latter should not be taken as an indication that they will be processed identically.

Related