Tags C & C++

A few months ago I've written an article about the (lack of) equivalence between pointers and arrays in C.

Here I want to tell about a related gotcha of the C language: passing 2D arrays around.

First of all, here's a code snippet that defines a function foo that prints out the contents of a dynamically allocated 2D array of integers:

void foo(int** arr, int m, int n)
{
    int i, j;
    for (i = 0; i < m; ++i)
    {
        for (j = 0; j < n; ++j)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

It's possible to try it with this code:

int** alloc_2d(int m, int n)
{
    int** arr = malloc(m * sizeof(*arr));
    int i;

    for (i = 0; i < m; ++i)
    {
        arr[i] = malloc(n * sizeof(**arr));
    }

    return arr;
}


int main()
{
    int** joe = alloc_2d(2, 3);

    joe[0][0] = 1;
    joe[0][1] = 2;
    joe[0][2] = 3;
    joe[1][0] = 4;
    joe[1][1] = 5;
    joe[1][2] = 6;

    return 0;
}

Now, suppose I want to use foo for printing out a statically allocated 2D array. Can I just write the following?

int moe[2][3];
moe[0][0] = 1;
moe[0][1] = 2;
moe[0][2] = 3;
moe[1][0] = 4;
moe[1][1] = 5;
moe[1][2] = 6;

foo(moe, 2, 3);

gcc complains:

array_2d_pointers.c:71: warning: passing argument 1 of 'foo' from incompatible pointer type
array_2d_pointers.c:8: note: expected 'int **' but argument is of type 'int (*)[3]'

And if I stubbornly run the code, I get a segmentation fault. Why?

Well, if you read that article I've just mentioned, the reason should become obvious.

foo expects a pointer to a pointer, but moe isn't a pointer to a pointer. Statically allocated 2D arrays are in fact one-dimensional chunks of memory laid out in row-major order. The compiler actually translates accesses to moe[i][j] simply as moe + i * n + j, where n is the amount of columns and moe, the array name, is just an alias for the memory location of the array.

In foo, the compiler will translate arr[i][j] to *(*(arr + i) + j)), so it will treat the contents of arr + i as an address to dereference, which it isn't. Thus the segmentation fault.

So how do we pass moe to a function? The most obvious way is to spell out its prototype in the function argument:

void bar(int arr[2][3], int m, int n)
{
    int i, j;
    for (i = 0; i < m; ++i)
    {
        for (j = 0; j < n; ++j)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

...
...

bar(moe, 2, 3) // valid call

This is not the only way, actually. In fact, the first dimension can be left out in such a declaration, so the following is also a valid declaration of bar:

void bar(int arr[][3], int m, int n)

As is the following:

void bar(int (*arr)[3], int m, int n)

This one is trickier. arr is actually a pointer to an array (a type I rather dislike). It is, however, equivalent to declaring int arr[][3].

So, now we know how to pass statically allocated 2D arrays to functions. How about 3D?

Well, it all gets quite repetitive from here on. Passing moe[2][3][4] to int*** is wrong. But it's OK to pass it to int arr[][3][4] or int (*arr)[3][4], et cetera.

To conclude, we should always remember that arrays are arrays, and pointers are pointers. Though similar in some aspects, they are not equivalent, and treating them as such is a common programming error.


Comments

comments powered by Disqus