I'm not aware of any contradiction in this area.
I'd go more with 'unclear'.
An array is pretty well defined: the compiler can't add padding in
between rows, or at the start or end. The size is what you expect
from a naive idea of 'array'. The sequence is defined; the compiler
can't store the rows 'upside-down' or 'backwards'.
So we have elements b[0][0], b[0][1], ..., b[1][0], ..., b[3][4]. We
can take addresses &b[0][0], &b[0][1], ..., &b[1][0], ..., &b[3][4], and
of course those have type 'int *'. And we have rows b[0], b[1], ...,
b[3]. We can take addresses &b[0], &b[1], ..., &b[3], and those have
type 'int (*)[5]'.
We can convert between the pointer types using a cast. What guarantees
do we have here? What things have to compare equal? Maybe things
compare equal but don't work the same? And we can do pointer
arithmetic. Maybe we do a lot of pointer arithmetic and convert between
a bunch of types, before a dereference.
We need the standard specify these, not use vague hand-wavy phrases,
leaving everyone to form their own opinion about what it looks like when
you connect all the puzzle pieces. We need to say what is defined or
undefined.
656 is weird because it talks about 'array subscript'. Everyone knows
the brackets operator is shorthand for pointer addition and dereference.
So it should talk about pointer addition and dereference. And then we
have cases where there is pointer addition independent of this, or many
steps before the dereference.
int sethi(char *c)
{
c[0] = 'h';
c[1] = 'i';
c[2] = 0;
}
We don't know that 'c[1]' is valid. The type here is 'char *', a
pointer to a character. So we just have to trust things here.
int boom()
{
char c;
/* here comes the 'boom' */
sethi(&c);
/* printf("%c\n", c); */
}
But this is a caller/callee disagreement. If it were pointer to array,
we could point our fingers better.
int beam()
{
char c[20];
c[0] = '*';
sethi(&c[1]);
strcat(&c[2], "\n");
/* fputs(c, stdout); */
}
Reverse of this, there's also the question of subtracting pointers where
arrays are involved. 656 doesn't appear to touch on this. When is it
defined and does what is fairly obvious? When is it undefined?
int sub(int x)
{
int b[4][5];
int *p;
int *q;
int d;
p = &b[0][4];
q = &b[1][0];
d = (int) (q - p);
/* printf("%d\n", d); */
return d;
}
The point is that the standard gives compilers latitude to do something
else.
Some rationale is not required, but it would be helpful. It is that
they think most uses that violate that rule are probably erroneous? It
is that you may produce better code? Do they think it is a case where
the programmer can do what he intends, but he should express it clearly?
Is it a case where they're being conservative, because the implications
of the opposite are not well understood?