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

Discussion in 'C Programming' started by Olumide, May 16, 2010.

  1. Olumide

    Olumide Guest

    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
     
    Olumide, May 16, 2010
    #1
    1. Advertising

  2. Olumide

    Seebs Guest

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

    On 2010-05-16, Olumide <> wrote:
    > 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
    --
    Copyright 2010, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
     
    Seebs, May 16, 2010
    #2
    1. Advertising

  3. Olumide

    David RF Guest

    Re: Why does passing two dimensional array to function acceptingpointer to a pointer generate an error?

    On 16 mayo, 08:37, Olumide <> wrote:
    > 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;
    }
     
    David RF, May 16, 2010
    #3
  4. Olumide

    Thad Smith Guest

    Re: Why does passing two dimensional array to function acceptingpointer to a pointer generate an error?

    David RF wrote:
    > On 16 mayo, 08:37, Olumide <> wrote:
    >> 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.

    --
    Thad
     
    Thad Smith, May 17, 2010
    #4
  5. Olumide

    David RF Guest

    Re: Why does passing two dimensional array to function acceptingpointer to a pointer generate an error?

    > It's better to call with "foo(array)".  Parameter array has exactly the correct
    > type required for foo().


    Are you sure?
     
    David RF, May 17, 2010
    #5
  6. Olumide

    David RF Guest

    Re: Why does passing two dimensional array to function acceptingpointer to a pointer generate an error?

    On 17 mayo, 07:59, David RF <> wrote:
    > > It's better to call with "foo(array)".  Parameter array has exactly the correct
    > > type required for foo().

    >
    > Are you sure?


    Yes, you're right, cast is superfluous.

    Thanks
     
    David RF, May 17, 2010
    #6
  7. Olumide

    John Bode Guest

    Re: Why does passing two dimensional array to function acceptingpointer to a pointer generate an error?

    On May 16, 1:37 am, Olumide <> wrote:
    > 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.
     
    John Bode, May 17, 2010
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Alf P. Steinbach
    Replies:
    0
    Views:
    444
    Alf P. Steinbach
    Aug 18, 2003
  2. John Harrison
    Replies:
    4
    Views:
    6,937
    Default User
    Aug 19, 2003
  3. Icosahedron
    Replies:
    8
    Views:
    667
    Vivek
    Aug 21, 2003
  4. Mr. SweatyFinger
    Replies:
    2
    Views:
    2,077
    Smokey Grindel
    Dec 2, 2006
  5. entitledX
    Replies:
    4
    Views:
    520
    Old Wolf
    Jan 12, 2006
Loading...

Share This Page