As verbose said , this gives undefined behavior. What follows is a higher accuracy.
5.2.1 / 1 says
[...] The expression E1 [E2] is identical (by definition) to * ((E1) + (E2))
Therefore, val[i] equivalent to *((val)+i)) . Since val is an array, conversion from array to pointer (4.2 / 1) occurs before the addition is done. Therefore, val[i] equivalent to *(ptr + i) , where ptr is int* set to &val[0] .
Then 5.7 / 2 explains what ptr + i indicates. He also says (emphasis mine):
[...] If both pointer operands and the result point to elements of the same array object or one after the last element of the array object, the evaluation should not lead to overflow; , undefined behavior .
In the case of ptr + i , ptr is the pointer operand, and the result is ptr + i . According to the quote above, both must point to an element of the array or to one last element. That is, in the case of OP, ptr + i is a well-defined expression for all i = 0, ..., 10 . Finally, *(ptr + i) well defined for 0 <= i < 10 , but not for i = 10 .
Edit
I am puzzled that val[10] (or, equivalently, *(ptr + 10) ) gives the behavior undefined or not (I am considering C ++, not C). In some cases this is true (for example, int x = val[10]; - the behavior is undefined), but in others it is not so clear. For instance,
int* p = &val[10];
As we have seen, this is equivalent to int* p = &*(ptr + 10); , which may be undefined behavior (since it shares a pointer to one of the last elements of val ) or the same as int* p = ptr + 10; which is well defined.
I found these two links that show how fuzzy this question is:
Can I take the address of a one-end-end element from an array?
Take the address of the element of the array "one after the past" through the index: legal according to the C ++ standard or not?