supporter_v
supporter_v

Reputation: 37

passing multi-dimensional arrays as function arguments

As we know, when passing arrays as function arguments, only the first dimension's size can be empty, the others must be specified.

void my_function(int arr[5][10][15]); // OKAY!
void my_function(int arr[][10][15]); // OKAY!
void my_function(int arr[][][15]); // WRONG!!!
void my_function(int arr[][][]); // WRONG!!!

What is logic behind this? Could someone explain the main reason?

Upvotes: 1

Views: 194

Answers (3)

John Bode
John Bode

Reputation: 123448

Starting with two bits of standardese:

6.3.2.1 Lvalues, arrays, and function designators
...
3 Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
...
6.7.6.3 Function declarators (including prototypes)
...
7 A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

C 2011 online draft

Lovely. What does all that mean?

Let's start with the declaration of an array of some arbitrary type T:

T arr[N];

The type of the expression arr is "N-element array of T" (T [N]). Unless that expression is the operand of the unary &, sizeof, or _Alignof operators as specified in 6.3.2.1/3 above, then the type of the expression is converted ("decays") to "pointer to T" (T *) and the value of the expression is the address of the first element - i.e., &arr[0].

When we pass arr as a parameter to a function, as in

foo( arr );

what foo actually receives is a pointer, not an array, and you would write the function prototype as

void foo( T *arr )

Or...

As a notational convenience, C allows you to declare the formal parameter using array notation:

void foo( T arr[N] )

or

void foo( T arr[] )

In this case, both T arr[N] and T arr[] are "adjusted" to T *arr as per 6.7.6.3/7 above, and all three forms declare arr as a pointer (this is only true for function parameter declarations).

That's easy enough to see for 1D arrays. But what about multidimensional arrays?

Let's replace T with an array type A [M]. Our declaration now becomes

A arr[N][M]; // we're creating N M-element arrays.

The type of the expression arr is "N-element array of M-element arrays of A" (A [M][N]). By the rule above, this "decays" to type pointer to *M-element array* ofA" (A (*)[M]`). So when we call

foo( arr );

the corresponding prototype is

void foo( A (*arr)[M] )1

which can also be written as

void foo( A arr[N][M] )

or

void foo( A arr[][M] );

Since T arr[N] and T arr[] are adjusted to T *arr, then A arr[N][M] and A arr[][M] are adjusted to A (*arr)[M].

Let's replace A with another array type, R [O]:

R arr[N][M][O];

The type of the expression arr decays to R (*)[M][O], so the prototype of foo can be written as

void foo( R (*arr)[M][O] )

or

void foo( R arr[N][M][O] )

or

void foo( R arr[][M][O] )

Are you starting to see the pattern yet?

When a multi-dimensional array expression "decays" to a pointer expression, only the first (leftmost) dimension is "lost", so in a function prototype declaration, only the leftmost dimension my be left blank.


  1. Since the subscript [] operator has a higher precedence than the unary * operator, A *arr[M] would be interpreted as "M-element array of pointers to A", which is not what we want. We have to explicitly group the * operator with the identifier to properly declare it as a pointer to an array.

Upvotes: 1

dbush
dbush

Reputation: 223739

Arrays in C and C++ are stored contiguously in memory. In the case of a multidimensional array, this means you have an array of arrays.

When you traverse an array, you don't necessarily need to know how many elements are in the array. You do however need to know the size of each element of the array so you can jump to the correct element.

That's why int arr[][][15] is incorrect. It says that arr is an array of unknown size elements are of type int [][15]. But this means you don't know how big each array element is, so you don't know where each array element it in memory.

Upvotes: 5

zneak
zneak

Reputation: 138041

Passing arrays to a function is an illusion: arrays instantly decay to a pointer when you use them. That is, with this example:

int foo[10];
foo[1];

the way this is interpreted by the compiler, in foo[1], foo is first transformed to a pointer to element 0 of foo, and then subscripting is the same as *(pointer_to_foo + 1).

However, this is only possible with the first dimension of an array. Suppose this:

int foo[5][5];

This puts 25 integer elements contiguously in memory. It is not the same as int** foo, and not convertible to int** foo, because int** foo represents some number of pointers to some number of pointers, and they don't have to be contiguous in memory.

While it's legal to use an array type as a function parameter, the compiler builds it identically to if you had specified a pointer to the array's element type:

int foo(int bar[10]); // identical to `int foo(int* bar)`

But what happens if you pass a multi-dimensional array?

int foo(int bar[5][5]); // identical to what?

Only the first dimension of the array can undergo decay. The equivalent signature for int foo here would be:

int foo(int (*bar)[5]); // identical to `int foo(int bar[5][5])`

where int (*bar)[5] is a pointer to an array of 5 integers. Stacking up more dimensions doesn't change the idea, only the first dimension will decay, and the other dimensions need to have a known size.

In other words, you can skip the first dimension because the compiler doesn't care about its size, as it instantly decays. However, subsequent dimensions do not decay at the call site, so you need to know their size.

Upvotes: 8

Related Questions