Passing a multidimensional variable length array to a function

ghz 1years ago ⋅ 3370 views

Question

There are tons of similar questions, but still I could not find any answer relevant for the feature of variable length arrays in C99/C11.

How to pass multidimensional variable length array to a function in C99/C11?

For example:

void foo(int n, int arr[][]) // <-- error here, how to fix?
{
}

void bar(int n)
{
    int arr[n][n];
    foo(n, arr);
}

Compiler (g++-4.7 -std=gnu++11) says:
error: declaration of ‘arr’ as multidimensional array must have bounds for all dimensions except the first

If I change it to int *arr[], compiler still complains:
error: cannot convert ‘int (*)[(((sizetype)(((ssizetype)n) + -1)) + 1)]’ to ‘int**’ for argument ‘2’ to ‘void foo(int, int**)’

Next question, how to pass it by value and how to pass it by reference? Apparently, usually you don't want the entire array to be copied when you pass it to a function.

With constant length arrays it's simple, since, as the "constant" implies, you should know the length when you declare the function:

void foo2(int n, int arr[][10]) // <-- ok
{
}

void bar2()
{
    int arr[10][10];
    foo2(10, arr);
}

I know, passing arrays to functions like this is not a best practice, and I don't like it at all. It is probably better to do with flat pointers, or objects (like std:vector) or somehow else. But still, I'm a bit curios what is the answer here from a theoretical standpoint.


Answer

Passing arrays to functions is a bit funny in C and C++. There are no rvalues of array types, so you're actually passing a pointer.

To address a 2D array (a real one, not array of arrays), you'll need to pass 2 chunks of data:

  • the pointer to where it starts
  • how wide one row is

And these are two separate values, be it C or C++ or with VLA or without or whatnot.

Some ways to write that:

Simplest, works everywhere but needs more manual work

void foo(int width, int* arr) {
    arr[x + y*width] = 5;
}

VLA, standard C99

void foo(int width, int arr[][width]) {
    arr[x][y] = 5;
}

VLA w/ reversed arguments, forward parameter declaration (GNU C extension)

void foo(int width; int arr[][width], int width) {
    arr[x][y]=5;
}

C++ w/ VLA (GNU C++ extension, terribly ugly)

void foo(int width, int* ptr) {
    typedef int arrtype[][width];
    arrtype& arr = *reinterpret_cast<arrtype*>(ptr);
    arr[x][y]=5;
}

Big remark:

The [x][y] notation with a 2D array works because the array's type contains the width. No VLA = array types must be fixed at compile-time.

Hence: If you can't use VLA, then...

  • there's no way to handle it in C,
  • there's no way to handle it without a proxy class w/ overloaded operator overloading in C++.

If you can use VLA (C99 or GNU C++ extensions), then...

  • you're in the green in C,
  • you still need a mess in C++, use classes instead.

For C++, boost::multi_array is a solid choice.

A workaround

For 2D arrays, you can make two separate allocations:

  • a 1D array of pointers to T (A)
  • a 2D array of T (B)

Then set the pointers in (A) to point into respective rows of (B).

With this setup, you can just pass (A) around as a simple T** and it will behave well with [x][y] indexing.

This solution is nice for 2D, but needs more and more boilerplate for higher dimensions. It's also slower than the VLA solution because of the extra layer of indirection.

You may also run into a similar solution with a separate allocation for everyB's row. In C this looks like a malloc-in-a-loop, and is analogous of C++'s vector-of-vectors. However this takes away the benefit of having the whole array in one block.