Jujitsu said:
James Kuyper said:
Jujitsu said:
You can avoid the constraint violation by changing the call to either
f(arr[0]);
or
f(&arr[0][0]);
The second form suggested would be my preferred one. Thanks for the
heads up.
But then you're invoking undefined behavior in f, though it's likely
to behave as you expect under most if not all implementations. Most
compilers will allow you to treat a two-dimensional array as a
one-dimensional array, but they're not required to do so. A compiler
that generates code that performs bounds checking, or whose
optimization phase is particularly aggressive, might result in
something other than the behavior you expect.
....
What I was trying to say is that if you have
long arr[3][5];
and you call a function defined as
void myfunc(long *q)
and you call it as
myfunc(&(arr[0][0]));
within that function it does something like:
q[14] = 0;
that should work per the definition of C.
"The definition of C" is the C standard, which specifies that this
code has undefined behavior. Therefore, "work per the definition of C"
doesn't tell you much about what such a program may do. Any behavior
"works per the definition of C", including aborting your program.
... In this case the "function
boundary" consists a pointer to a long. I don't believe the compiler may
make assumptions about _which_ pointer to long may be passed or about how
large the index on q[] might be. But I have to look this up.
q[14] is defined as being equivalent to *(q+14). When an object is
part of an array, the rules of pointer arithmetic (6.5.6p8) define
what the range of integer values is that can be added to a pointer to
that object. Those rules are based upon the size of the array and the
position of the object within that array. An object that is not a part
of an array is treated, for the purpose of these rules, as if it were
the only element of a 1-element array.
The key point is, those rules cannot be interpreted, in this context,
as referring to the array 'arr'. If such an interpretation were
possible, then the part of those rules which tells you where q+14
points to, would mean that it points at arr[14], which is nonsense.
The array that those rules refer to must have an element type that is
the same as the type pointed at by the pointer. The only array
containing arr[0][0] with that property is arr[0], not arr itself. The
element type of arr is char[5], not char. arr[0] has a length of 5,
not a length of 15.
Therefore, in order to add an integer value 'i' to &arr[0][0], what
those rules require is that 0<=i && i<=5. Otherwise, the behavior is
undefined. Those rules also specify that, while you can add 5 to &arr
[0][0], any attempt to dereference that pointer has undefined
behavior.
The compiler has to honor the function boundary. The function accepts a
pointer to long. It should treat it as if it could be ANY pointer to
long.
Not quite. For instance, consider the following example:
long arr[3][5];
int add_arr(int *p, int *q)
The parameter "q was added at an intermediate stage when I was
thinking about demonstrating this issue in a different fashion. I
should have removed it when I changed my mind. Please ignore it.
{
for(int row=0; row<3; row++)
for(int col=0; col<3; col++)
arr[row][col] += p[12];
}
Now, in the general case, an implementation is required to generate code
for functions like add_arr that works correctly even if p points at a
location within "arr". "Correctly" means, in this case, that if one of +=
operations changes the value referred to by p[12], then the next pass
through the loop should use the updated value.
However, in this particular case, there's no legal way to pass a value 'p'
to add_arr such that p[12] points to an element of arr. That's because the
largest amount that you can add to any pointer that points to any element
of 'arr' is 5; add more than that and you're in a different sub-array of
arr, violating
Sorry: that should have ended with the citation of 6.5.6p8.
As a result, an implementation is free to perform an optimization that
reads the value of p[12] just once, and keeps that value in a register for
the entire loop.
That's a rather contrived example, but its the best I could com up with
right now.
....
I do like your example. I understand it.
If what you say is correct, I have a lot of reading to do.
Personally, I wouldn't think twice about using the function above in the way
you indicated is a violation. If you are correct, that means I'm dangerous
and I need to retrain myself. I'm not being sarcastic there--I'm serious.
My mental model of arrays in C is that they are defined to be in row-major
order JUST SO THAT YOU CAN MONKEY WITH THEM IN UNINTENDED WAYS. My
understanding of C is that every array is really one-dimensional and the
possibility of multidimensional arrays is just for programmer convenience.
That last part, at least, is correct. In this case, 'arr' is a one-
dimensional array whose element type is char[5]. arr[0] is a one-
dimensional array whose element type is char. The standard refers to
arr as a 'multidimensional' array, but the way C handles such arrays
means that they are really just arrays of arrays. What difference does
that distinction make? In languages with true multidimensional arrays,
it's just as easy (syntactically, at least) to refer to a column of an
array as to a row. There's no C syntax for referring to a column of
arr.