If we have an array: int foo[5][6];
there are 3 types that might be used with this.
int (*p1)[5][6] = &foo;
which is a pointer to the full array. Such a pointer could also be used
to point into int bar[4][5][6];
There is also int (*p2)[6] = &foo[n]; which points to one row of the
array, and
int *p3 = &foo[n][m]; which points to an element of foo;
Note that for p1 set equal to &foo p1++, p1--, p1+1, or p1[1] are all
invalid operations, as p1 doesn't point into an array, but a single
object (that happens to be an array).
We have the ability to set p2 = &p1[1] to move down the dimensions, and
if the pointers are fat in that they store the base and limits, all that
information can be derived from the value in p1. What is problematic is
converting a p2 to a p1, but this can only be done via a cast in C (of
use of void* s implicit casting), but it should be noted that this type
of pointer cast is inherently very prone to "undefined behavior" so
language trying to be C like, but avoiding/limiting undefined behavior
might just prohibit this action or warn on the construct and allow
undefined behavior thereafter. Note that even in C this sort of
upcasting of an element to the array is fairly rare, and very prone to
invoking undefined behavior.
void* pointers need to be limited if not eliminated. The void pointer
needs to either be a source of undefined behavior, or it needs to
remember the bounds of the object it pointed into (and maybe of the full
object it is pointing into), making its type punning ability much less
useful (as it becomes disallowed).
It is possible to add checking for this sort of thing, if you generate
for every array (single or multidimensional) a table describing it, and
include in the pointer information on where in the array you are
pointing. Then the cast operation could look into this data an see if
the up cast is valid and if so the new bounds.
For run time generated arrays, malloc and family would need to be
smarter, and somehow be passed the type of the array rather than just a
byte count (it would become a bit more like the C++ new operator), so
that it could return the appropriate fat pointer, since that is the only
can of pointer that can point into an array.
For a pointer to a single object as opposed to into an array, one
solution would be to consider such an object as part of a 1 dimensional
array. If you are using the option of pointer up casting and needing
description blocks for arrays, you can either create a block of all such
objects that have their address taken, or define a special case that a
value of 0 for the pointer to the array block is a signal that the
pointer isn't into an array (and thus upcasting, or manipulating the
address are illegal).
The comment on overhead is comparing to C. A C pointer is typically 1
register big (or at least 1 address register big), and tends to be able
to be manipulated with simple direct machine instructions. The fat
pointer needed to avoid undefined behavior needs to be bigger, as it
must store extra information. The operations on it will typically not be
a simple direct machine instruction (unless the machine is one designed
for this sort of purpose, where address manipulations include bounds
testing) so is slower.
C code attempting to duplicate this bounds checking would be slower than
C code without all this checking, but possible faster than the language
using fat pointers. One reason being that the programmer can possible
know more about what pointers point into and perhaps come up with better
test to "prove" that the accesses are valid. In normal C code, a lot of
accesses the programmer can "prove" to himself that they are safe from
other guarantees, that the compiler might not be able to prove for
itself (like know that all strings do have a null terminator, and thus a
char scanning loop is safe). Of course the danger is that the programmer
also might make a mistake.