Why does passing two dimensional array to function accepting pointerto a pointer generate an error?

O

Olumide

Hi -

Would someone kindly tell me why the following bit of code generates a
runtime error:

....

void foo( int **data )
{
data[0][0];
}

int array[3][3]; /* data can be of any size */
foo( array ); /* compile-time error */
foo( (int**)array ); /* run-time error */

Thanks,

- Olumide
 
S

Seebs

Would someone kindly tell me why the following bit of code generates a
runtime error:

It should get a compile-time error.
int array[3][3]; /* data can be of any size */
foo( array ); /* compile-time error */
foo( (int**)array ); /* run-time error */

It generates an error because it's wrong.

Conceptually, a 2D array looks like this:

array = [ [ a, b, c ], [ d, e, f], [ g, h, i] ]

array[0] is really the array [a, b, c]. But! When you use the
expression "array[0]", the array is implicitly converted into a pointer
to its first element (in most contexts; not, say, in a sizeof()).

So array[0][0] is interpreted as array (understood to be a pointer to the
block [a, b, c]), indexed by 0 (getting us the actual object [a, b, c]),
which in turn is then interpreted as a pointer to its first element (a),
which is then dereferenced, yielding the actual object a.

If you had an int **, though, it would look like this:

ptrptr = &level1
level1 = &[ level2sub1, level2sub2, level2sub3 ]
level2sub1 = &[ a, b, c ]
level2sub2 = &[ d, e, f ]
level2sub3 = &[ g, h, i ]

When you write ptrptr[0][0], the interpretation is different. ptrptr is
already a pointer to level1. The variable ptrptr doesn't actually refer to
the block of memory containing those three other pointers; it is actually
an address to begin with, rather than being an object that is converted to
an address when you use it in an expression. Similarly, ptrptr[0] isn't
an object which contains a, b, and c -- it's an object which contains a
single address, which describes where a, b, and c are stored.

To experiment with this a bit, try:

int array[10];
int *ptr;
printf("array is %d bytes\n", (int) sizeof(array));
printf("pointer is %d bytes\n", (int) sizeof(ptr));

Now, think a bit about this:
int array[10];
void foo(int *a) {
}

...
foo(array);

Why does this work? It works because "array" is converted into a pointer
to array[0]. That's good enough to be mostly interchangeable with any
other pointer. But if you do:
int array[10][10];
void foo(int **a) {
}
...
foo(array);

it's not quite right. foo() is expecting a pointer to a series of pointers
to ints. array is converted, not into a pointer to a bunch of pointers, but
into a pointer to a series of arrays. If foo() tries to look at the second
member of its argument a, it calculates where in an array of pointers the
second pointer would be, then uses the bits it finds there as a pointer,
which it thinks will point to some ints. Instead, you've given it something
where (most likely) the bits that it takes are actually one of the members
of array[0], so it's not a pointer at all.

Basically, you lied to the compiler, and it got its revenge. :)

-s
 
D

David RF

Hi -

Would someone kindly tell me why the following bit of code generates a
runtime error:

...

As seebs says you lied to the compiler, instead of (int **) you can:

#include <stdio.h>

void foo(int data[][3])
{
printf("%d\n", data[0][0]);
printf("%d\n", data[1][1]);
printf("%d\n", data[2][2]);

}

int main(void)
{
int array[][3] = {{1,1,1},{2,2,2},{3,3,3}};

foo((int (*)[]) array);
return 0;
}

or ...

#include <stdio.h>

void foo(int *data[])
{
printf("%d\n", data[0][0]);
printf("%d\n", data[1][1]);
printf("%d\n", data[2][2]);

}

int main(void)
{
int array[][3] = {{1,1,1},{2,2,2},{3,3,3}};
int *p[3] = {array[0], array[1], array[2]};

foo(p);
return 0;
}
 
T

Thad Smith

David said:
Hi -

Would someone kindly tell me why the following bit of code generates a
runtime error:

...

As seebs says you lied to the compiler, instead of (int **) you can:

#include <stdio.h>

void foo(int data[][3])
{
printf("%d\n", data[0][0]);
printf("%d\n", data[1][1]);
printf("%d\n", data[2][2]);

}

int main(void)
{
int array[][3] = {{1,1,1},{2,2,2},{3,3,3}};

foo((int (*)[]) array);
return 0;
}

It's better to call with "foo(array)". Parameter array has exactly the correct
type required for foo(). Adding the cast suppresses a warning if the parameter
and argument types don't match.
 
J

John Bode

Hi -

Would someone kindly tell me why the following bit of code generates a
runtime error:

...

void foo( int **data )
{
    data[0][0];

}

int array[3][3];     /* data can be of any size */
foo( array );         /* compile-time error */
foo( (int**)array ); /* run-time error */

Thanks,

- Olumide

The types of "array" (int (*)[3]) and "data" (int **) are not
compatible, which is why you get the compile-time error. Using the
cast to shut the compiler up doesn't address the underlying
incompatibility between the two types, which is why you get the
runtime error.

In most contexts, an expression of type "N-element array of T" is
implicitly converted ("decays") to type "pointer to T" and evaluates
to the location of the first element in the array (the exceptions to
this rule are when the array expression is an operand of the sizeof or
address-of (&) operators, or if the array expression is a string
literal being used to initialize another array in a declaration).
Thus, the expression "array" in the call "foo(array);" decays from
type "3-element array of 3-element array of int" to type "pointer to 3-
element array of int", or "int (*)[3]", which is *not* the same as
"int **".

Thus, the signature of "foo" needs to be either

foo (int (*data)[3])

or

foo (int data[][3])

In the context of a function parameter declaration, "T a[]" is
equivalent to "T *a", so "T a[][N]" is equivalent to "T (*a)[N]".
Either way, you can index "data" normally (as in data[0][0], data[1]
[1], etc.). The subscript operator implicitly dereferences the
pointer, since "a" is defined as "*(a+i)".

Note that this means foo() can only deal with Nx3 arrays of int. If
you want foo() to handle any arbitrary NxM size array, you'll have to
do something different. One trick that *may* work is to explicitly
pass a pointer to the first element of the array and pass both
dimensions as separate parameters:

void foo(int *data, size_t d0, size_t d1)
{
size_t i, j;
for (i = 0; i < d0; i++)
for(j = 0; j < d1; j++)
data[i * d1 + j] = ...;
}

int main(void)
{
int array[3][3];
size_t d0 = sizeof array / sizeof array[0];
size_t d1 = sizeof array[0] / sizeof array[0][0];
...
foo(&array[0][0], d0, d1);
...
}

Note that we treat "data" as a 1-d array of int. To access an element
at index (i,j) we compute the offset as "i * d1 + j" as opposed to "
[j]".

Here's a handy table for figuring out types of expressions involving
arrays given an array type:

Declaration: T a[N]

Expression Type Decays Result
---------- ---- ------ ----------
a T [N] T * Location of first
element (&a[0])
&a T (*)[N] Location of a
sizeof a size_t Number of bytes in a
== sizeof T * N
a T Value at index i
&a T * Location of a

Declaration: T a[N][M]

Expression Type Decays Result
---------- ---- ------ ----------
a T [N][M] T (*)[M] Location of first
element (&a[0])
&a T (*)[N][M] Location of a
sizeof a size_t Number of bytes in a
== sizeof T * N * M
a T [M] T * Array at index i
&a T (*)[M] Location of array at
index i
sizeof a size_t Number of bytes in a
== sizeof T * M
a[j] T Value at index i,j
&a[j] T * Location of a[j]

Note that the location of the array and the location of the first
element of the array evaluate to the same *value*, but have different
*types*. That is, given the declaration

T a[N];

the expressions "a" and "&a" resolve to the same location (address of
the first element), but the types are "T *" and "T (*)[N]",
respectively.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top