Re: Copying an array slice (Was: Re: Difficulties with passing multi-dimensional arrays)
- From: Netocrat <netocrat@xxxxxxxxxxx>
- Date: Tue, 28 Jun 2005 00:50:43 +1000
On Mon, 27 Jun 2005 06:58:02 +0000, Chris Torek wrote:
>>On Fri, 17 Jun 2005 05:56:11 +0000, Dave Thompson wrote:
>>> Or in C99 or in GNU C as an extension use a VLA which does this for
>>> you. It's easiest to pass the bound(s) _first_: void set_slice (int
>>> fullwidth, int (*ary) [fullwidth] ) /* or int ary [] [fullwidth] if you
>>> prefer, it's exactly equivalent */ { ... access ary[0:3][0:3] ... }
>>> ... set_slice (8, bg) /* accesses bg[0:3][0:3] */ /* or better use
>>> something like Me's MAT_WIDTH macro */ set_slice (8, &bg[2]) /*
>>> accesses bg[2:5][0:3] */ set_slice (8, (int(*)[8])&bg[2][2]) /*
>>> bg[2:5][2:5] */ /* this last case is yucky and if at all frequent
>>> would be handled better by saving an offset pointer or by going
>>> through a flattened pointer as below */
>
> This, combined with the code sample Netocrat gave here, which I excerpt:
>
> In article <pan.2005.06.19.18.52.27.924325@xxxxxxxxxxx> Netocrat
> <netocrat@xxxxxxxxxxx> wrote:
>>void print_slice(int fullwidth, int (*ary)[fullwidth], int width, int
>>height) {
> ... [snippage]
>>}
> ...
>> int a2d[H][W];
> ...
>> print_slice(W, (int(*)[])&a2d[SL_Y][SL_X], SL_W, SL_H);
>
> suggests a new use (in C99) for a type that was largely useless in C89:
> the pointer to array of unknown size.
>
Yes it's very nifty to use in cases like this. I have often wanted a way
to access arbitrary smaller portions of arbitrary sized arrays but I
didn't come across a portable way to do it without VLAs.
> The trick here is that the type "pointer to array ? of T" (where T is
> some valid array element type, and ? denotes that the size is unknown)
> is compatible with the type "pointer to array n of T", where T is the
> same element-type and n is any integer expression (in C99 only -- n must
> be a constant in C89). The latter is one of these new C99 "variable
> length array" objects -- VLAs.
That's a neat trick; the neater trick for me though was what Dave Thompson
pointed out and that I hadn't known was possible prior to that:
int a2d[100][200];
int (*array2)[200];
array2 = &a2d[23][45]; /* for example */
/* or to avoid warnings, with a cast */
array2 = (int (*)[200])&a2d[23][45];
array2 is now an array with - using the term you introduced - the
same stride as the original array, and based at the original array's
element [23][45].
We can now access elements [23..99][45..200] of the original array by
using corresponding indices in the new array of [0..76][0..155]. Of course
there's nothing to stop us extending the second element access to the
range 156..199 but semantically there I haven't found reason to do so as
yet.
It all makes sense mathematically, I just didn't (a) conceive of it and
(b) know that such code would be accepted by standard C.
> In C89, the requirement that N be a constant meant that there was no way
> to use "pointer to array ? of T" that would not be served just as well
> by a value of the simpler type "pointer to T". The reason is that if
> you have a "pointer to array ?", the only thing you can do with this
> pointer is follow it, naming the "array ? of T" object to which it
> points, or of course assign it to another (compatible) pointer. If the
> pointer points to just the one single array, and you follow that
> pointer, you get an "array N of T" object, which -- for all practical
> purposes -- then falls under The Rule about pointers and arrays in C,
> and becomes a value of type "pointer to T", pointing to the first
> element of the array. That is:
>
> int array[5];
> int (*ap)[] = &array; /* this is OK */ int *ip = &array[0]; /* but
> so is this */
>
> /* or we can do this: */
> ip = &(*ap)[0];
>
> /* and now any place we can write (*ap)[i], we can write ip[i] to
> the same effect */
>
> A pointer to an entire array becomes useful when we know the size of the
> array to which it points:
>
> int arr2[3][5];
> int (*ap2)[5] = &arr2[0];
>
> Now ap2[i][j] has the same effect as arr2[i][j]: we move to the i'th row
> and j'th column. The C compiler knows how to reach the i'th row only
> because it knows the size of each complete row -- what we call the
> "array stride" in compiler-geek-speak.
>
> In C89 (which lacks VLAs), array strides are always -- *always* --
> constants. In C99, with its new VLAs, array strides *can* be variables,
> and in print_slice() above, the array stride is in fact a variable
> ("fullwidth").
>
> The annoyance occurs in the call to print_slice(), which, as shown
> above, has to use a cast.
For the declaration that my code used:
int a2d[H][W];
The following code compiles without warnings or errors:
print_slice(W, a2d, W, H);
print_slice(W, &a2d[1], W, H - 1);
And for the following code:
print_slice(W, &a2d[SL_Y][SL_X], SL_W, SL_H);
gcc generates a warning about passing an argument "from incompatible
pointer type"; but it still compiles without a cast.
Adding a cast as per:
print_slice(W, (int(*)[])&a2d[SL_Y][SL_X], SL_W, SL_H);
removes the warning. So according to gcc, a cast isn't required, but a
warning is. I don't know if this is as per the standard...
When you say that the call to print_slice as you quoted at the message
start "has to use a cast" I'm interpreting that to expand to "has to use a
cast to avoid a compiler warning", not "has to use a cast in order to
compile under a standards-compliant compiler". Correct?
> We can fix this problem in C99. Admittedly, this comes at the expense of
> type-checking, but we pay the same cost when we use casts -- casts tell
> a compiler "please don't complain about the type, the programmer knows
> what he's doing, nothing can go wrong / go wrong / go wrong..." -- so
> there is no real loss here.
Hmmm, I'm not sure that we actually do lose any type-checking. After all,
type-checking occurs at compile-time yet in the case of VLAs the length is
unknown at compile time so nothing can be checked. Of course in some
cases the length is constant at compile time and theoretically could be
checked, but gcc at least doesn't seem to. For reference, the options I
have used for all code tested for this post are:
-W -Wall -std=c99 -pedantic
The following compiles without warnings (using my original definition
of print_slice):
print_slice(W + 1, &a2d[1], W, H - 1);
&a2d[1] is a pointer to array W of int. The function prototype expects
the second parameter to be pointer to array fullwidth of int. In this
case, fullwidth is W + 1. So we have:
expected: int (*)[W + 1]
actual : int (*)[W]
All of this is known at compile time, yet there is no warning. I wouldn't
expect the standard to require a warning in this case since the second
parameter is variable length and not necessarily known at compile time -
it just so happens that in this case it is constant at compile time - a
warning would be useful but not necessary.
> Let us rewrite print_slice() to take, as its second parameter, the type
> "pointer to array ? of int" (and while I am at it, I will change the
> name slightly):
>
> void print_slice(int fullwidth, int (*ary0)[], int width, int
> height) {
> ...
> }
> }
> Next, we add the "missing" width to the VLA, but do it inside the
> function:
>
> void print_slice(int fullwidth, int (*ary0)[], int width, int
> height) {
> int (*ary)[fullwidth];
> ...
> }
I presume that you mean to set ary equal to ary0, as in:
int (*ary)[fullwidth] = ary0;
> Now we can -- at least sometimes -- call print_slice() without using a
> cast:
>
> print_slice(W, a2d, m, n); /* prints a2d[0..m)[0..n), where n<=W */
I don't think the requirements for casting have changed at all with your
new function prototype - although I may have missed something. For
example the above call could equally be made (without using a cast) with
the old prototype. Even substituting &a2d[x] for a2d works the same with
both prototypes without using a cast.
So your new prototype doesn't seem to be different in any way from the
original in terms of type checking or casting requirements.
I would say that loss of type-checking is not its drawback; instead the
requirement for an extra automatic variable is. No doubt the smallest
level of compiler optimisation will, however, remove this variable.
Alternately one could access the original variable through a cast ... more
cumbersome but removes the need for an extra automatic variable. Either
way, it's not a big drawback. :)
> This also eliminates the constraint Dave Thompson noted (that the array's
> second dimension must occur in the argument list before the array itself),
> as we can now write:
>
> void f(int (*ap0)[], int fullwidth, int width, int height) { ... }
This is the true and - as far as I can see - only - benefit of removing
the fullwidth dimension from the array parameter. Nevertheless it's a
worthwhile one. I like it.
> for instance, because we reconstruct the stride inside the body of the
> function.
>
> Of course, none of this lets us start at the j'th element, a2d[i][j],
> without a cast.
As I've already explained I think that we can start at an arbitrary
element without the requirement for a cast (assuming you first take the
address of the element as in &a2d[i][j]), although we will get a
warning without one. I'm going by gcc's behaviour and my intuition that
it is correct - rather than the standard - which I have neither access to
nor the motivation to read through to find all rules that would apply to
this situation. Thankfully there are no shortage of people on this list
who know the standard well enough without that I don't have to do that.
> The only way to go that far is to resort to "void *":
You wouldn't need to go as far as void * would you? I think int * would
suffice.
Anyway I think you are right that there is no way to avoid a warning in
this case without a cast or redefining the function as you have below. In
doing so though we _do_ lose the type-checking that we previously had for
the cases of a2d or &a2d[i]. I think that the loss of type-checking in
these case outweighs the benefit of not requiring a cast for the
&a2d[i][j] case.
I would be impressed if someone could find a way to remove all warnings
without requiring any casting and without the loss of any type-checking in
the cases of a2d and &a2d[i].
> void g(int fullwidth, void *ap0, int width, int height) {
> int (*ap)[fullwidth] = ap0;
> ...
> }
> }
> but this does not illustrate a new use for "pointer to array ? of T". :-)
.
- References:
- Difficulties with passing multi-dimensional arrays
- From: truckaxle
- Re: Difficulties with passing multi-dimensional arrays
- From: clayne
- Re: Difficulties with passing multi-dimensional arrays
- From: Dave Thompson
- Copying an array slice (Was: Re: Difficulties with passing multi-dimensional arrays)
- From: Netocrat
- Re: Copying an array slice (Was: Re: Difficulties with passing multi-dimensional arrays)
- From: Chris Torek
- Difficulties with passing multi-dimensional arrays
- Prev by Date: Re: "basic" pointer question
- Next by Date: Re: About c++
- Previous by thread: Re: Copying an array slice (Was: Re: Difficulties with passing multi-dimensional arrays)
- Next by thread: difftime problem
- Index(es):
Relevant Pages
|