By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
432,070 Members | 1,718 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 432,070 IT Pros & Developers. It's quick & easy.

Difficulties with passing multi-dimensional arrays

P: n/a
I am trying to pass a slice from a larger 2-dimensional array to a
function that will work on a smaller region of the array space. The
code below is a distillation of what I am trying to accomplish.

// - - - - - - - - begin code - - - - - - -
typedef int sm_t[4][4];
typedef int bg_t[8][8];

sm_t sm;
bg_t bg;

void set_slice (sm_t);

int main ()
{
set_slice( (sm_t *)&bg[0][0]);
}

set_slice (sm_t my_sm)
{
static int i;
i++;

my_sm[0][0] = i;
}
// - - - - - - - - end code - - - - - - -

This code works however I have to comment out the prototype to compile
under gcc. If I add the prototype I get compile errors with

mar.c:30: warning: passing arg 1 of `set_slice' from incompatible
pointer type
mar.c:41: conflicting types for `set_slice'
mar.c:16: previous declaration of `set_slice'

Thanks in advance for any guidance.

Nov 14 '05 #1
Share this Question
Share on Google+
11 Replies


P: n/a
Me
> typedef int sm_t[4][4];
typedef int bg_t[8][8];

sm_t sm;
bg_t bg;

void set_slice (sm_t);

int main ()
{
set_slice( (sm_t *)&bg[0][0]);
}
Changing the typedefs to what it looks like internally:

void set_slice(int (*)[4]);

set_slice( (int (*)[4][4]) &bg[0][0] );

You're passing a pointer to a 4x4 matrix to a function expecting a
pointer to a 4 element array, now you can see why the compiler bitches.

6.7.5.2/6 "For two array types to be compatible, both shall have
compatible element types, and if both size specifiers are present, and
are integer constant expressions, then both size specifiers shall have
the same constant value. If the two array types are used in a context
which requires them to be compatible, it is undefined behavior if the
two size specifiers evaluate to unequal values."

Playing with casts like this breaks the C aliasing rules. GCC is pretty
anal about aliasing and has been known to generate code which gives
unwary programmers incorrect results (and even causes crashes).
set_slice (sm_t my_sm)


you're missing the void return type here. C99 got rid of implicit int
(which is incompatible with the prototype anyway).

Here is how I would do it:

#define MAT_WIDTH(x) (sizeof(x[0])/sizeof((x)[0][0]))

void ones(int *mat, size_t height, size_t width, size_t pitch)
{
assert(width <= pitch);

for (size_t h = 0; h < height; ++h) {
int *m = mat[h*pitch];
for (size_t w = 0; w < width; ++w)
*m++ = 1;
}
}

/*
Note: you cannot replace mat[h*pitch] with mat += pitch, it is illegal
to do that in general because C only guarantees 1 past the end of the
array.
*/

int bg[8][8] = { };
ones(&bg[4][4], 4, 3, MAT_WIDTH(bg));

/*
00000000
00000000
00000000
00000000
00001110
00001110
00001110
00001110
*/

Nov 14 '05 #2

P: n/a
truckaxle wrote:
void set_slice (sm_t);
set_slice (sm_t my_sm)
First one returns void.
Second one returns int.

That is a type mismatch so it's additionally throwing off the prev decl
error because it thinks you're trying to additionally define a new
function with a pre-existing name.
{
static int i;
i++;

my_sm[0][0] = i;
}
// - - - - - - - - end code - - - - - - - mar.c:30: warning: passing arg 1 of `set_slice' from incompatible
pointer type
Because you're trying to pass a pointer to a function that
doesn't want a pointer.
mar.c:41: conflicting types for `set_slice'
mar.c:16: previous declaration of `set_slice'

Thanks in advance for any guidance.


This is what you want:

typedef int sm_t[4][4];
typedef int bg_t[8][8];

sm_t sm;
bg_t bg;

void set_slice (sm_t *);

int main (void)
{
set_slice( (sm_t *)&bg[0][0]);

return (0);
}
void set_slice (sm_t *my_sm)
{
static int i;
i++;

*my_sm[0][0] = i;
}

Nov 14 '05 #3

P: n/a
On 10 Jun 2005 14:52:58 -0700, cl****@anodized.com wrote:
truckaxle wrote:
void set_slice (sm_t);
set_slice (sm_t my_sm)
First one returns void.
Second one returns int.

That is a type mismatch so it's additionally throwing off the prev decl
error because it thinks you're trying to additionally define a new
function with a pre-existing name.

It's a type mismatch and must be diagnosed. You are in fact trying to
define the function which was previously just declared; if the
compiler believes that it is correct.
{
static int i;
i++;

my_sm[0][0] = i;
}
// - - - - - - - - end code - - - - - - -

mar.c:30: warning: passing arg 1 of `set_slice' from incompatible
pointer type


Because you're trying to pass a pointer to a function that
doesn't want a pointer.

Actually it does want a pointer, because int[4][4] as a parameter is
'adjusted' to int(*)[4]. But it doesn't want the _type_ of pointer you
are trying to give it, as the diagnostic says.
mar.c:41: conflicting types for `set_slice'
mar.c:16: previous declaration of `set_slice'

Thanks in advance for any guidance.


This is what you want:

typedef int sm_t[4][4];
typedef int bg_t[8][8];

sm_t sm;
bg_t bg;

There doesn't seem any need for sm (in the OP code).
void set_slice (sm_t *);

int main (void)
{
set_slice( (sm_t *)&bg[0][0]);

return (0);
}
void set_slice (sm_t *my_sm)
{
static int i;
i++;

*my_sm[0][0] = i;
}


This parses as * (my_sm [0] [0]) and so works only for row 0. Trying
to do *my_sm[1][2] or such would invoke Undefined Behavior most likely
manifested as accessing (clobbering) totally wrong storage.

(*my_sm)[x][y] would correctly access an sm_t object embedded within
the bg_t object bg -- but not a _slice_ as the OP asked for: it will
treat the 16 ints starting at bg[4][4] to wit bg[4][4:7] bg[5][0:7]
bg[6][0:3] as a 4 by 4 matrix with e.g. (*my_sm)[3][1] at bg[6][1].

If you really want a (>1D) _slice_ you have to do the stride
calculation; possibly manually as "Me" did nextthread, except missing
one unary-& and the false distinction between ptr + stride and
&ptr[stride] -- they are defined as identical and hyperpedantically
neither is guaranteed to work beyond a subarray (row) but in practice
they (both) work.

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 */

If you really want the array parameter first, you must either regress
to K&R1 syntax:
void set_slice (ary, fullwidth)
int fullwidth;
int ary [] [fullwidth];
{ ... }

or in GNU C only use their extension syntax:
void set_slice (int fullwidth; int ary [] [fullwidth], int fullwidth )
{ ... }

or go through a "flattened" pointer:
void set_slice (int * flat, int fullwidth)
{ int (* ary) [fullwidth] = ( int (*) [fullwidth] ) flat; ... }
/* here for a nonparameter canNOT use int ary [] [width] */
which also makes it easier to pass a slice that is not at
the beginning of a row (second subscript nonzero).

- David.Thompson1 at worldnet.att.net
Nov 14 '05 #4

P: n/a
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 */


Nice one, I haven't seen that before. It gave me an idea for a function
to extract an arbitrary-size slice from an arbitrary-size 2D array and I
coded it. Not being familiar with the standards as many here seem to be,
I'd like to know whether this code is standard C and how portable it is.
Also if anyone could suggest which of the alternatives would generally be
optimal / better practice where I've commented: /* OR i * W + j; */ that
would be great. The first would seem to involve less calculation but
perhaps compiler optimisations would remove that seeming benefit - I know
it's hard to generalise about this sort of thing.

This code compiles under gcc with no warnings with options -Wall -std=c99
-pedantic and gives the expected output:

main array: width 10, height 5
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
slice in main array at X=4; Y=3; width 3, height 2
34 35 36
44 45 46
extracting that slice from main array...
extracted slice:
34 35 36
44 45 46

Here's the code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define W 10
#define H 5
#define SL_W 3
#define SL_H 2
#define SL_X 4
#define SL_Y 3

void print_slice(int fullwidth, int (*ary)[fullwidth], int width, int height)
{
int i, j;

for (i = 0; i < height; i++) {
for (j = 0; j < width; j++)
printf("%3d ", ary[i][j]);
printf("\n");
}
}

int *make_slice(int fullwidth, int (*ary)[fullwidth], int width, int height)
{
int (*slicep)[height][width];
int i;
size_t row_sz = sizeof((*slicep)[0]);

if (! (slicep = malloc(sizeof(*slicep))) )
exit(EXIT_FAILURE);
for (i = 0; i < height; i++)
memcpy(&(*slicep)[i], &ary[i], row_sz);
return (int *)slicep;
}

int main(int argc, char **argv)
{
int a2d[H][W];
int (*slicep)[SL_H][SL_W];
int i, j, k=0;

/* set array values */
for (i = 0; i < H; i++) {
for (j = 0; j < W; j++)
a2d[i][j] = k++; /* OR i * W + j; */
}

printf("main array: width %d, height %d\n", W, H);
print_slice(W, a2d, W, H);

printf("slice in main array at X=%d; Y=%d; width %d, height %d\n",
SL_X, SL_Y, SL_W, SL_H);
print_slice(W, (int(*)[])&a2d[SL_Y][SL_X], SL_W, SL_H);

printf("extracting that slice from main array...\n");
slicep = (int (*)[SL_H][SL_W])make_slice(W,
(int(*)[])&a2d[SL_Y][SL_X], SL_W, SL_H);

printf("extracted slice:\n");
print_slice(SL_W, *slicep, SL_W, SL_H);

free(slicep);
return EXIT_SUCCESS;
}

Nov 14 '05 #5

P: n/a
>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 <pa****************************@dodo.com.au>
Netocrat <ne******@dodo.com.au> 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.

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.

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. 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.

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];
...
}

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 */

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) { ... }

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. The only way to go that far is to resort to "void *":

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". :-)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (4039.22'N, 11150.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.
Nov 15 '05 #6

P: n/a
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 <pa****************************@dodo.com.au> Netocrat
<ne******@dodo.com.au> 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". :-)


Nov 15 '05 #7

P: n/a
On Tue, 28 Jun 2005 00:50:43 +1000, Netocrat <ne******@dodo.com.au>
wrote:
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_:
<snip examples which got garbled, look upthread if needed, except>
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 <add> by which I meant void* */

This, combined with the code sample Netocrat gave here, which I excerpt:

In article <pa****************************@dodo.com.au> Netocrat
<ne******@dodo.com.au> 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. <snip: because compatible with pointer to array of any known size _OR_
VLA (and same element type), hence convertible w/o cast>
<snip: about stride much more detailed (as usual!) than mine>

Aside: doesn't that locution "the pointer to array of unknown size"
sound like it should be an SF or maybe mystery novel title? <G>
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?

Without the cast you are passing an int*, which happens to point to
memory within the larger 2D array (hence) laid out so it can be used
as int (*)[W], to int(*)[W]. According to the standard those types are
incompatible and as with essentially all other things the standard
dislikes a 'diagnostic' is required. Whether a diagnostic is an error
or a warning, or even applying electrical shock to your nose, is
outside the scope of the standard and up to the implementation.
(Implementors choosing the third option are pretty unpopular though.)

In the "tradition of C" most compilers, including gcc, generally try
to proceed with compilation if there is a reasonable, "probably"
right, and implementable interpretation of your code, as in this case.
But that isn't required. In gcc, adding -Werror will cause warnings to
make compilation fail, as will -pedantic-error instead of -pedantic
for the "standard-only" ones, if you want that.
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. <snip example of VLA based on argument that is constant in one call> 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.
But this is only one case, arguably an unusual/unlikely one that is
not worth spending much effort on, especially as _this_ case could as
easily have been written with a constant and thus checked size.
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): <snip and then 'convert' to a right-typed local>
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.

Concur.
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. :)

I would call this no drawback at all; as you say any decent maybe even
halfdecent compiler will optimize it away. I do consider it a very
tiny nuisance to have to think about which identifier -- the actual
parameter or the local copy -- should get the tweaked name, since I
don't do this often enough to have evolved a firm policy.
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.

Concur. I noted the other less palatable ways to do this.

<snip rest>

- David.Thompson1 at worldnet.att.net
Nov 15 '05 #8

P: n/a
On Mon, 04 Jul 2005 05:38:50 +0000, Dave Thompson wrote:
On Tue, 28 Jun 2005 00:50:43 +1000, Netocrat <ne******@dodo.com.au> wrote:
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_: <snip examples which got garbled, look upthread if needed, except>
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 <add> by which I meant void* */
> This, combined with the code sample Netocrat gave here, which I
> excerpt:
>
> In article <pa****************************@dodo.com.au> Netocrat
> <ne******@dodo.com.au> 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. <snip: because compatible with pointer to array of any known size _OR_ VLA
(and same element type), hence convertible w/o cast> <snip: about stride
much more detailed (as usual!) than mine>

Aside: doesn't that locution "the pointer to array of unknown size" sound
like it should be an SF or maybe mystery novel title? <G>
It comes across something like 'The mysterious clue in the boundless
array'. Except a little less tacky.
> 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. <snip example of VLA based on argument that is constant in one call>
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.

But this is only one case, arguably an unusual/unlikely one that is not
worth spending much effort on, especially as _this_ case could as easily
have been written with a constant and thus checked size.


Sure, I agree. I just wanted to point out the theoretically possibility.
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. :)

I would call this no drawback at all; as you say any decent maybe even
halfdecent compiler will optimize it away. I do consider it a very tiny
nuisance to have to think about which identifier -- the actual parameter
or the local copy -- should get the tweaked name, since I don't do this
often enough to have evolved a firm policy.


In those cases I prefer to keep the prototype "pristine" and muddy the
local copy... like putting on your best clothes when you present yourself
to the public and being a little more casual at home.
> 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.

Concur. I noted the other less palatable ways to do this.


Unfortunately this doesn't extend to arrays of greater than two
dimensions. I rewrote the code for 3d arrays, and it is not possible
according to my reading of the C99 draft to specify a function prototype
as:
void print_3d_slice(int (*aryin)[][], int d2, int d1, int i3, int i2, int i1)

Under section "6.7.5.2 Array declarators" it reads The element type shall not be an incomplete or function type.

and that would seem to preclude a declaration of the form:
int array[][].

It is possible to write and compile such code under gcc - without warning
in default mode and with a warning in C99 mode. The code works as
expected but it's not standards compliant.

The minimalist left-most placement of the array parameter for an
n-dimensional array is to specify the sizes of the 2nd to n-1th dimension
sizes before the array parameter, and the nth after it.

In the case of three dimensions, this is:
void print_3d_slice(int d2, int (*aryin)[d2][], int d1, int i3, int i2, int i1)

Both of these prototypes require the real type array variable inside
the function and the assignment of the parameter to the inner variable.

Or we can revert to the original syntax and have all dimension sizes
before the array parameter (without requirement for a variable); or some
other variation (in the case of more than 3 dimensions).

The other very useful feature of variable-length arrays when combined with
C90's allowance for mixing declarations and code is that we can create
dynamically allocated multi-dimensional arrays with the same storage
characteristics and syntax access as their statically allocated
counterparts. I'm sure I'm not the first person to comment on this, but
it's something that I think is very useful since I've always considered the
requirement for arrays of pointers to be a waste of space in a traditional
C90 dynamically-allocated 3d array based on the declaration:

int ***dynamic3darray;

Unfortunately it seems that C99 is not likely to
be widely adopted anytime soon according to the mood in clc.

The comments in the following code - which extends the code I first posted
to 3d arrays - illustrate this new type of dynamically allocated array:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
void print_3d_slice_minimal(int d2, int (*aryin)[d2][], int d1, int i3, int i2,
int i1)
void print_3d_slice_gcc(int (*aryin)[][], int d2, int d1, int i3, int i2,
int i1)
*/
void print_3d_slice(int d2, int d1, int (*ary)[d2][d1], int i3, int i2, int i1)
{
int i, j, k;
/* uncomment if using either of the alternative function prototypes
int (*ary)[d2][d1] = aryin;
*/
for (i = 0; i < i3; i++) {
for (j = 0; j < i2; j++) {
for (k = 0; k < i1; k++)
printf("%3d ", ary[i][j][k]);
printf("\n");
}
printf("\n");
}
}

int *make_3d_slice(int d2, int d1, int (*ary)[d2][d1], int i3, int i2, int i1)
{
int (*slicep)[i3][i2][i1];
int i, j;
size_t sz1 = sizeof(***slicep);

if (! (slicep = malloc(sizeof(*slicep))) ) {
perror("malloc");
return NULL;
}
/** Could be optimised for the case where i1 == d1 if we make the
* assumption that the passed-in element starts at the beginning of the
* row
*/
for (i = 0; i < i3; i++) {
for (j = 0; j < i2; j++)
memcpy(&(*slicep)[i][j], &ary[i][j], sz1);
}

return (int *)slicep;
}

int *alloc_user_3d_array(int *d3, int *d2, int *d1)
{
printf("Enter the 3 sizes of the array dimensions: ");
scanf("%d %d %d", d1, d2, d3);
int (*a3d)[*d2][*d1];
if (!(a3d = malloc(sizeof(***a3d) * *d3 * *d2 * *d1)))
perror("malloc");
return (int *)a3d; /** we can't return the actual type since we can't
specify an array as a return type **/
}

void get_user_slice_dims(int *sl1, int *sl2, int *sl3, int *sl1st, int *sl2st,
int *sl3st)
{
printf("Enter the 3 indices of the slice start: ");
scanf("%d %d %d", sl1st, sl2st, sl3st);
printf("Enter the 3 sizes of the slice dimensions: ");
scanf("%d %d %d", sl1, sl2, sl3);
}

int main(void)
{
int d1, d2, d3, i, j, k, v = 0;
int sl1, sl2, sl3, sl1st, sl2st, sl3st;
int *retval;

/** Here we dynamically allocate a 3d array with size specified by
* user-input. This is only possible in C99; in C90 it is illegal for
* two reasons:
* (1) declaring a3d after the function call is prohibited and
* (2) variable-sized arrays are illegal and a3d is variable-sized
* since the function call can change the variables d2 and d1.
*/
retval = alloc_user_3d_array(&d3, &d2, &d1);
if (!retval)
exit(EXIT_FAILURE);
int (*a3d)[d2][d1] = (int (*)[d2][d1])retval;

for (i = 0; i < d3; i++) {
for (j = 0; j < d2; j++)
for (k = 0; k < d1; k++)
/** We can access a3d as we would any
* statically allocated array, however it is
* dynamically allocated and uses the same
* amount of storage as a statically allocated
* array - the traditional C90 approach would
* be to declare int ***a3d and have to allocate
* two layers of pointers - wasteful of space
* in terms of both memory and source code.
*/
a3d[i][j][k] = ++v;
}

printf("main array: d3 %d, d2 %d, d1 %d \n", d3, d2, d1);
print_3d_slice(d2, d1, a3d, d3, d2, d1);

get_user_slice_dims(&sl1, &sl2, &sl3, &sl1st, &sl2st, &sl3st);
printf("slice in main array at d3 %d..%d; d2 %d..%d; d1 %d..%d\n",
sl3st, sl3st + sl3 - 1, sl2st, sl2st + sl2 - 1, sl1st,
sl1st + sl1 - 1);
print_3d_slice(d2, d1, (int (*)[d2][d1])&a3d[sl3st][sl2st][sl1st],
sl3, sl2, sl1);

printf("extracting that slice from main array...\n");
/** Again we dynamically allocate a 3d array with the same access syntax
* and storage as a statically allocated array
*/
int (*slice3d)[sl2][sl1];
slice3d = (int (*)[sl2][sl1])make_3d_slice(d2, d1,
(int (*)[d2][d1])&a3d[sl3st][sl2st][sl1st], sl3, sl2, sl1);
if (!slice3d)
exit(EXIT_FAILURE);

printf("extracted slice:\n");
print_3d_slice(sl2, sl1, slice3d, sl3, sl2, sl1);

free(a3d);
free(slice3d);
exit(EXIT_SUCCESS);
}

Nov 15 '05 #9

P: n/a
On Wed, 06 Jul 2005 18:36:52 +1000, Netocrat <ne******@dodo.com.au>
wrote:

<snip: rather nested Chris's version using pointer to unbounded array>
Unfortunately this doesn't extend to arrays of greater than two
dimensions. I rewrote the code for 3d arrays, and it is not possible
according to my reading of the C99 draft to specify a function prototype
as:
void print_3d_slice(int (*aryin)[][], int d2, int d1, int i3, int i2, int i1)
Right.
Under section "6.7.5.2 Array declarators" it reads
The element type shall not be an incomplete or function type. and that would seem to preclude a declaration of the form:
int array[][].

It is possible to write and compile such code under gcc - without warning
in default mode and with a warning in C99 mode. The code works as
expected but it's not standards compliant.

In C99 this is a constraint violation, and thus required diagnostic,
which gcc can make a warning. In C90 it was not a constraint, and
effectively undefined by omission; 6.1.2.5 (now 6.2.5p20) doesn't
allow an actual object of T[][], and arguably not an actual type
T[][], but since C90 didn't specify timing of the 'adjustment' of
parameters declared as an array or function, it was legal and arguably
made sense to accept T[][] as T(*)[] which was and is legal.

In fact it was possible to accept rank>2 e.g. T[][][] as long as the
actual array (of unspecified bound) objects are never used as
themselves but always allowed to decay.
The minimalist left-most placement of the array parameter for an
n-dimensional array is to specify the sizes of the 2nd to n-1th dimension
sizes before the array parameter, and the nth after it.

In the case of three dimensions, this is:
void print_3d_slice(int d2, int (*aryin)[d2][], int d1, int i3, int i2, int i1)

Both of these prototypes require the real type array variable inside
the function and the assignment of the parameter to the inner variable.
The minimal left-most placement is to specify no dimensions at all and
use a flattened T* or even void*. This also has the (IMO good) feature
of treating all dimensions the same way.
Or we can revert to the original syntax and have all dimension sizes
before the array parameter (without requirement for a variable); or some
other variation (in the case of more than 3 dimensions).

The other very useful feature of variable-length arrays when combined with
C90's allowance for mixing declarations and code is that we can create
dynamically allocated multi-dimensional arrays with the same storage
characteristics and syntax access as their statically allocated
counterparts. I'm sure I'm not the first person to comment on this, but
it's something that I think is very useful since I've always considered the
requirement for arrays of pointers to be a waste of space in a traditional
C90 dynamically-allocated 3d array based on the declaration:

int ***dynamic3darray;
You mean _C99_ mixed declarations and statements. Although this can be
avoided trivially though rather uglily by just introducing new blocks
wherever needed; IIRC this was what original (cfront) C++/CwC did to
support declaration-anywhere using C that didn't have it at that time.
Unfortunately it seems that C99 is not likely to
be widely adopted anytime soon according to the mood in clc.

It is not at all clear that Usenet, and especially writers (as opposed
to reader-onlies) on Usenet, is/are representative. But even if so,
what matters here is not whether compiler-users use it or even ask for
it but whether implementors support it -- and particularly whether
they support these particular features, which gcc already does/did,
setting a de-facto standard that others have some motivation to meet.

<snip rest>

- David.Thompson1 at worldnet.att.net
Nov 15 '05 #10

P: n/a
On Fri, 15 Jul 2005 04:44:24 +0000, Dave Thompson wrote:
On Wed, 06 Jul 2005 18:36:52 +1000, Netocrat <ne******@dodo.com.au>
wrote:

<snip: rather nested Chris's version using pointer to unbounded array>
Unfortunately this doesn't extend to arrays of greater than two
dimensions. I rewrote the code for 3d arrays, and it is not possible
according to my reading of the C99 draft to specify a function
prototype as:
void print_3d_slice(int (*aryin)[][], int d2, int d1, int i3, int i2,
int i1)
Right.
Under section "6.7.5.2 Array declarators" it reads
> The element type shall not be an incomplete or function type.

and that would seem to preclude a declaration of the form: int
array[][].

It is possible to write and compile such code under gcc - without
warning in default mode and with a warning in C99 mode. The code works
as expected but it's not standards compliant.

In C99 this is a constraint violation, and thus required diagnostic,
which gcc can make a warning. In C90 it was not a constraint, and
effectively undefined by omission; 6.1.2.5 (now 6.2.5p20) doesn't allow
an actual object of T[][], and arguably not an actual type T[][], but
since C90 didn't specify timing of the 'adjustment' of parameters
declared as an array or function, it was legal and arguably made sense
to accept T[][] as T(*)[] which was and is legal.

In fact it was possible to accept rank>2 e.g. T[][][] as long as the
actual array (of unspecified bound) objects are never used as themselves
but always allowed to decay.


Something like this?

void accept_decayed_array(int (*x)[][]) {...} void
take_incomplete_array(int x[][][]) {
/* x decays into int (*)[][] on entry */
int (*y)[10][10] = x; /* (*)[10][10] is compatible with (*)[][]
* as far as I can see */
accept_decayed_array(x); /* illustrating that the decay has
* occurred */
}

I'm wary of the fact that gcc is giving me a warning re incomplete element
type on both function prototypes even in C90 mode and even omitting -W and
-Wall. I know that any warnings can be issued at the compiler's
discretion, but it's not reassuring. It seems like it's a construct
that's best to avoid given that it's explicitly forbidden in C99, and
hence likely future standards.
The minimalist left-most placement of the array parameter for an
n-dimensional array is to specify the sizes of the 2nd to n-1th
dimension sizes before the array parameter, and the nth after it.

In the case of three dimensions, this is: void print_3d_slice(int d2,
int (*aryin)[d2][], int d1, int i3, int i2, int i1)

Both of these prototypes require the real type array variable inside
the function and the assignment of the parameter to the inner variable.

The minimal left-most placement is to specify no dimensions at all and
use a flattened T* or even void*. This also has the (IMO good) feature
of treating all dimensions the same way.


I neglected that approach but you're right, there's good benefit in having
a single prototype for all number of dimensions.
Or we can revert to the original syntax and have all dimension sizes
before the array parameter (without requirement for a variable); or
some other variation (in the case of more than 3 dimensions).

The other very useful feature of variable-length arrays when combined
with C90's allowance for mixing declarations and code is that we can
create dynamically allocated multi-dimensional arrays with the same
storage characteristics and syntax access as their statically allocated
counterparts. I'm sure I'm not the first person to comment on this,
but it's something that I think is very useful since I've always
considered the requirement for arrays of pointers to be a waste of
space in a traditional C90 dynamically-allocated 3d array based on the
declaration:

int ***dynamic3darray;

You mean _C99_ mixed declarations and statements.


Yes I do. Typo.
Although this can be
avoided trivially though rather uglily by just introducing new blocks
wherever needed; IIRC this was what original (cfront) C++/CwC did to
support declaration-anywhere using C that didn't have it at that time.


I'm curious about how introducing new blocks can be used to get around the
inability to declare variable arrays in C90. Can you give an example?

<snip>
Nov 15 '05 #11

P: n/a
On Fri, 15 Jul 2005 17:41:44 +1000, Netocrat <ne******@dodo.com.au>
wrote:
On Fri, 15 Jul 2005 04:44:24 +0000, Dave Thompson wrote: <snip>
In fact [in C90] it was possible to accept rank>2 e.g. T[][][] as long as the
actual array (of unspecified bound) objects are never used as themselves
but always allowed to decay.


Something like this?

void accept_decayed_array(int (*x)[][]) {...} void
take_incomplete_array(int x[][][]) {
/* x decays into int (*)[][] on entry */
int (*y)[10][10] = x; /* (*)[10][10] is compatible with (*)[][]
* as far as I can see */
accept_decayed_array(x); /* illustrating that the decay has
* occurred */


Or just access e.g. y[i][j][k], right.
}

I'm wary of the fact that gcc is giving me a warning re incomplete element
type on both function prototypes even in C90 mode and even omitting -W and
-Wall. I know that any warnings can be issued at the compiler's
discretion, but it's not reassuring. It seems like it's a construct
that's best to avoid given that it's explicitly forbidden in C99, and
hence likely future standards.

It does work in the gcc versions I have to hand, and only some of them
warn. But not in some other compilers; it is only permitted not
required in C90, where it is easily enough worked around, and is
indeed prohibited in C99 where as already discussed we do have VLA
types, so go ahead and be wary.

<snip>
You mean _C99_ mixed declarations and statements.


Yes I do. Typo.
Although this can be
avoided trivially though rather uglily by just introducing new blocks
wherever needed; IIRC this was what original (cfront) C++/CwC did to
support declaration-anywhere using C that didn't have it at that time.


I'm curious about how introducing new blocks can be used to get around the
inability to declare variable arrays in C90. Can you give an example?

Not VLAs. Extra blocks in C90 allow you to achieve the effect of mixed
declarations and statements. As was first needed for C++/CwC.
- David.Thompson1 at worldnet.att.net
Nov 15 '05 #12

This discussion thread is closed

Replies have been disabled for this discussion.