'two_d_array[0]' thus has array type. That means that for anything
other than 'sizeof' or '&', it becomes 'int *'. That pointer points
to a valid object and so does incrementing that pointer by '20'.
Yes, I know that is your position. There is no point in our repeating
the same things over and over.
But since I haven't pointed it out yet:
Type is not all there is to a pointer.
Agreed.
There is also a question of what
object the pointer points into.
Agreed.
two_d_array[0] is an array[10],
Agreed. Taking as valid our declaration, the sub-expression
'two_d_array[0]' is an expression with the type "array of int with ten
elements." Another useful notation in addition to Peter's could be
'int[10]'. Section 6.7.6 of the C standard draft with filename
'n1256.pdf'.
so when
it decays into a pointer, it decays into a pointer into that array of 10
objects.
Agreed...
It could possibly be confusing for a reader of the draft when
regarding "array type" versus "array object," if only "array" is
mentioned. For example, 5.1.2.2.1,p2 "argv array", where "object" is
perhaps intended (there's no mention of 'argv' as a type).
Also, if only "an array object" is specified (as in 6.5.6,p8), it
might not be clear to a reader whether _any_ array object will do, or
if only _a_particular_ array object will do.
Unfortunately, 6.5.6,p8 results in a circular definition for "Array
subscripting", 6.5.2.1,p2, which defines the subscript operator in
terms including pointer arithmetic via the binary '+' operator, who
then defines pointer arithmetic in terms including the "difference of
the subscripts". Oops.
For an object with "allocated" "storage duration" (6.2.4,p1), there
isn't even a "declared type" for an array object to work with, but
only the type of an lvalue used to access it (6.5,p6). If we accept
"only _a_particular_ array object" above, it might be difficult to
accept any accesses to elements within an array object with allocated
storage, since:
The effective type is the type of the lvalue used to access the
object, in the case of an object with allocated storage.
An implementation is welcome to detect attempts to move outside
the boundaries of that array.
Agreed. Some bounds of arrays are known at translation-time and some
are not, also.
Furthermore, the decay Peter describes above looks like:
int[10] ---> int *
If an implementation attempts to keep track of the 'int *'-typed
result as "must point within an object with type 'int[10]'" instead of
discarding the bound, we can at least work around this by casting the
result to 'void *' or 'char *', then to 'int(*)[sizeof two_d_array /
sizeof two_d_array[0][0]]', then back to 'void *' or 'char *', then
back to an 'int *' like we started with. The middle-measure should
discard any bound the implementation might have been attempting to
track.
/* should be fine, this is guaranteed to be true */
assert((two_d_array[0] + 10) == &(two_d_array[1][0]));
two_d_array[1][0] = 1; /* valid */
two_d_array[0][10] = 1; /* undefined behavior */
Well at least there's a work-around (detailed above) for concerns of
UB, here.
Comparison between pointers does not compare their boundaries. (The assert
is safe because you are allowed to calculate the address of the object one
past the end of an array.)
I would suggest that implementations should attempt to work with the
knowledge of which locations are valid for which object types
('two_d_array' being declared for 100 contiguous 'int's), OR to drop
any tracked bound during the conversion of 'int[10]' to 'int *'. Just
a suggestion.
In a four-dimensional array:
int fd[10][10][10][10];
'fd' (alone) is an expression with type 'int[10][10][10][10]', which
might become 'int(*)[10][10][10]'.
'fd[0]' (alone) is an expression with type 'int[10][10][10]', which
might become 'int(*)[10][10]'.
'fd[0][0]' (alone) is an expression with type 'int[10][10]', which
might become 'int(*)[10]'.
'fd[0][0][0]' (alone) is an expression with type 'int[10]', which
might become 'int *'.