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