James Tursa said:
[...]
No. There cannot be padding between array elements; in particular,
given:
double arr[10][10];
the size of arr is guaranteed to be exactly 100*sizeof(double).
Padding isn't the issue.
Well, I didn't really believe that padding was an issue but that's
what seem to be implied by the response.
The issue is that the standard doesn't
require implementations to support indexing past the end of an array.
So if I write
arr[0][15]
I'm trying to refer to an element of arr[0] that doesn't exist.
There's a valid object, accessible as arr[1][5], at the intended
location in memory -- and *most* C compilers will let you access that
object either as arr[0][15] or as arr[1][5]. But arr[1][5] is
guaranteed to work, and arr[0][15] isn't, because it attempts to index
beyond the end of the double[10] array arr[0].
In other words, implementations are allowed, but not required, to
perform bounds checking.
Well, I am still trying to understand how that argument applies to the
original OP posted code. Your argument is based on using arr directly.
But you seem to be saying that OP can't do this:
double *dp = (double *) arr;
and then traverse the entire array using dp. Is that what you are
saying?
Close. I'm saying that you most likely *can* get away with that
(treating an array of array of double as if it were an array of
double), but the standard doesn't require an implementation to make it
work. The most likely ways it can fail are if an implementation
performs run-time or compile-time bounds checking, or if an optimizer
assumes (as it's permitted to do) that you're not doing something like
this, causing the generated code not to do what you expected it to do.
Harald's explanation elsewhere in this thread makes the point more
clearly than I did, I think:
The fact that double[2][3] doesn't have elements such as
x[0][5]. There must be a valid double, 5*sizeof(double) bytes into
x. However, x[0][5] doesn't mean just that. x[0][5] (or
((double*)x)[5]) means you're looking 5*sizeof(double) bytes into
x[0]. x[0] doesn't have that many elements.
I get the part about x[0][5] maybe not working.
But the only way I can make sense of everything else that everyone is
trying to tell me is that there are special rules for unsigned char
that prevent bounds checking for this type of code that don't apply to
other types. For example:
double d[2][3];
double *dp = (double *) d;
unsigned char *ucp = (unsigned char *) d;
double *xdp = malloc(sizeof(d));
unsigned char *xucp = malloc(sizeof(d));
mydoublecopy(xdp,dp,6); // assume prototype present
myucharcopy(xucp,ucp,6*sizeof(double));
where
void mydoublecopy(double *t, double *s, size_t n) {
while( n-- ) { *t++ = *s++; } }
void myucharcopy(unsigned char *t, unsigned char *s, size_t n) {
while( n-- ) { *t++ = *s++; } }
What you are saying is that mydoublecopy isn't guaranteed to work
because the compiler is free to bounds check the result of s++ and see
if it violates the bounds of the original d[0] row. i.e., it is free
to recognize that the current value of s is within the d[0] row and
then bounds check the s++ result and cause a fault if it is beyond the
end of that row and you try to dereference it. And this may happen
regardless of any typecasting that I may have done in an explicit
attempt to aviod this d[0] check, and regardless of the fact that the
compiler may know for certain that the memory for d[0] and d[1] is
contiguous.
But myucharcopy *is* guaranteed to work even though it also violates
the bounds of the original d[0] row in the s++ calculation. It seems
the standard must explicitly forbid bounds checking like this for
unsigned char type in order for this to work.
It this what you are saying? There are special rules for unsigned char
here (which would seem to then raise other questions)?
James Tursa