This behavior is undefined, and here's why.
Access to a multidimensional array can be divided into a series of one-dimensional array accesses. In other words, the expression a[i][j] can be represented as (a[i])[j] . Citation C11 ยง6.5.2.1 / 2:
The definition of the index operator [] is that E1[E2] identical (*((E1)+(E2))) .
This means that the above is identical to *(*(a + i) + j) . Following C11 ยง6.5.6 / 8 regarding the addition of an integer and a pointer (emphasis mine):
If the operand pointer and the result point to elements of the same array object or one after the last element of the array object, the estimate should not lead to overflow; otherwise, the behavior is undefined.
In other words, if a[i] not a valid index, the behavior occurs immediately undefined, even if the "intuitive" a[i][j] is represented within the boundaries.
So, in the first case, a[0] valid, but the next [20] not, because the type of a[0] is int[5] . Therefore, the index 20 goes beyond.
In the second case, a[-1] already beyond, thus, already UB.
In the latter case, however, the expression a[5] points to one past of the last element of the array, which is true in accordance with ยง 6.5.5 / 8:
... if expression P points to the last element of an array object, expression (P)+1 points one after the last element of an array object ...
However, later in the same paragraph:
If the result points to the last element of an array object, it should not be used as the operand of the unary * operator that is being evaluated.
So, although a[5] is a valid pointer, dereferencing it will lead to undefined behavior caused by the final indexing [-3] (which is also out of bounds, therefore UB).