Pointers vs. arrays in C, part 2(D)

April 6th, 2010 at 2:17 pm

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.

Related posts:

  1. Pointers to arrays in C
  2. Are pointers and arrays equivalent in C?
  3. making sense of pointers
  4. Reading C type declarations
  5. Allocating multi-dimensional arrays in C++

11 Responses to “Pointers vs. arrays in C, part 2(D)”

  1. ChrisNo Gravatar Says:

    At least modern compilers have (for the most part) all converged on the de facto standard of row-major order.

  2. elibenNo Gravatar Says:

    @Chris,

    Row-major order of multidimensional arrays is defined by the C standard.

  3. HelgeNo Gravatar Says:

    In C99 you can do the following:

    void fn(int m, int n, double arr[m][n])
    {
    int j, k;
    for(j=0; j<m; j++) {
    for(k=0; k<n; k++) printf("%lf ", arr[j][k]);
    printf("\n");
    }
    }

    This greatly simplifies all of the variable-sized multi-dimensional array passing.

  4. elibenNo Gravatar Says:

    @Helge,

    Sadly, support for C99 is far from being ubiquitous. I wouldn’t trade this convenience for portability. On a related note, there’s quite some criticism on the VLAs feature of C99. I’m not sure they’re worth the effort

  5. HelgeNo Gravatar Says:

    @eliben:

    the “criticism” of VLAs is that C99 does not specify where to allocate storage (stack or heap) for automatic variables, which might lead to leaks, but this “problem” does not apply for function arguments.

    It might also help to put a name on the elephant in the room: “not ubiquitous” in this context means “MSVC does not support it”, all other important compilers (pgi, intel, gcc) work just fine.

  6. xavierNo Gravatar Says:

    Who wants to use more than 1D-arrays??

    What is the benefict of 2D (or more) arrays in C? None.

  7. elibenNo Gravatar Says:

    @xavier,

    I hope you’re kidding :-) 2D arrays in C are quite useful to represent matrices, images, tables, graphs and so on. Even 3D and higher dimensions are needed from time to time, especially for scientific applications.

  8. kyriakosNo Gravatar Says:

    Actually, you can use:
    void bar(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*n+j]);
    }
    printf("\n");
    }

    and call as : bar(arr, 2, 3);, where arr is a statically allocated array
    (i.e. int arr[2][3]={..})

    The compiler will complain sure enough, but you will not get a segfault and the program will happily execute as intended.

  9. AR89No Gravatar Says:

    I didin’t understand what happen behind. Suppose that I have:
    int arr[2][3]={..}

    and those two functions:
    void bar(int *a, int m, int n)
    void bar(int (*a)[3], int m, int n)
    If I pass arr to the first one, inside that function I will operate with a pointer to the first element of the array, but what will I have if I pass it to the second one? Where does a pointer to array points? (I suppose it points to the first elements of the array).

  10. elibenNo Gravatar Says:

    AR89,

    Yes, the pointer contains the address of the first element of the array. Remember that an address is an address, the type just tells the compiler how to interpret it. In other words, the compiler understand the type and thus can translate accesses to the variable to the appropriate memory addressing. Thus if you have a+1, the result depends on the type of a. I encourage you to copy the code from this post and experiment with the values of the pointers. printf("%p") is your friend here, or just use GDB.

  11. AR89No Gravatar Says:

    I tried an experiment as you suggested:

    #include <stdlib.h>
    #include <stdio.h>
    
    void first_bar(int *a, int n, int m){
        printf("%p\n", a);
    }
    
    void bar(int (*a)[3], int n, int m){
        printf("%p\n", a);
    }
    
    int main(){
        int m[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
        first_bar(m, 3, 3);
        bar(m, 3,3 );
        return 0;
    }

    Here is the output:
    0x7fff504d0b90
    0x7fff504d0b90

    The pointer passed is the same, I’ll run some loop to see what it does in each function

Leave a Reply

To post code with preserved formatting, enclose it in `backticks` (even multiple lines)