Ben said:
I can't find the other message so I'll have to ask here. Are you
saying that converting a double * to, say, unsigned char * permits one
to access parts of an array that are not accessible via the double *?
What are the limits on addition that one is exempted from and where is
this permission granted?
The description of how addition of an integer to a pointer works in
6.5.6p8 is entirely in terms of positions within (and one beyond the end
of) an array of the pointed-at type. The only array of 'double' declared
anywhere in the program that contains the position pointed at by
(double*)a is a[0]. Therefore, the limits based upon array length
imposed by 6.5.6p8 refer to the length of the array a[0] (which is 1),
not the length of 'a' itself (which is 2), and not the length of a
1-dimensional array which could have been allocated in the same memory
as 'a', which would have had a length of 2x1 == 2.
The special characteristic of unsigned char* is that 6.2.6.1p4 defines
C's object model, for non-character types with a size of 'n' bytes, in
terms of treating them as arrays of n unsigned chars - such an array is
defined as constituting the _object representation_ of such types. The
standard guarantees that an object may be copied into such an array, and
gives memcpy() as an example of how this may be done. The fact that
memcpy is given as an example, rather than requiring the use of memcpy()
to perform such a copy, implies that if I were to write a function named
my_memcpy() which matched the other specifications given by the standard
for memcpy(), it must also be usable for copying the object
representation. In other words, the ability to copy object
representations is not a magical additional ability of memcpy(), but is
merely a side-effect of the fact that the behavior of memcpy() is
defined in terms of copying arrays of unsigned char.
If run-time bounds checking were applied to unsigned char* in the most
extreme way otherwise permitted by 6.5.6p8, it would prevent my_memcpy()
from working properly. The undefined behavior allowed by 6.5.6p8 for
adding too large of an integer value to a pointer value, is trumped by
the defined behavior provided by 6.2.6.1p4 for copying an entire object
as an array of unsigned char, even if that object is a multi-dimensional
array of some other type.
You go on to say that these limits are meaningless for void *, but at
some point, useful void *s are converted back. Do the limits that are
then imposed derive from the original pointer or can they get lost?
I.e. is (double *)(void *)dp different in what it can access to
(double *)(void *)(unsigned char *)dp?
The standard is not at all clear about what happens in most pointer
conversions, including those. However, I see no technical difficulty
with retaining the bounds-checking information inside a pointer
throughout a long series of intermediate pointer conversions. The
bounds-checking information cannot be used if the current pointer type
is "unsigned char*", and it's meaningless if the current pointer type
"void*", but it can still reside inside such pointers, hidden, waiting
for conversion to a type which does permit run-time bounds-checking. It
is 6.5.6p8 which makes such bounds-checking legal.