Why do we need to specify the column size when passing a 2D array as a parameter?

40,246

Solution 1

When it comes to describing parameters, arrays always decay into pointers to their first element.

When you pass an array declared as int Array[3] to the function void foo(int array[]), it decays into a pointer to the beginning of the array i.e. int *Array;. Btw, you can describe a parameter as int array[3] or int array[6] or even int *array - all these will be equivalent and you can pass any integer array without problems.

In case of arrays of arrays (2D arrays), it decays to a pointer to its first element as well, which happens to be a single dimensional array i.e. we get int (*Array)[3].

Specifying the size here is important. If it were not mandatory, there won't be any way for compiler to know how to deal with expression Array[2][1], for example.

To dereference that a compiler needs to compute the offset of the item we need in a contiguous block of memory (int Array[2][3] is a contiguous block of integers), which should be easy for pointers. If a is a pointer, then a[N] is expanded as start_address_in_a + N * size_of_item_being_pointed_by_a. In case of expression Array[2][1] inside a function (we want to access this element) the Array is a pointer to a single dimensional array and the same formula applies. The number of bytes in the last square bracket is required to find size_of_item_being_pointed_by_a. If we had just Array[][] it would be impossible to find it out and hence impossible to dereference an array element we need.

Without the size, pointers arithmetics wouldn't work for arrays of arrays. What address would Array + 2 produce: advance the address in Array 2 bytes ahead (wrong) or advance the pointer 3* sizeof(int) * 2 bytes ahead?

Solution 2

In C/C++, even 2-D arrays are stored sequentially, one row after another in memory. So, when you have (in a single function):

int a[5][3];
int *head;

head = &a[0][0];
a[2][1] = 2; // <--

The element you are actually accessing with a[2][1] is *(head + 2*3 + 1), cause sequentially, that element is after 3 elements of the 0 row, and 3 elements of the 1 row, and then one more index further.

If you declare a function like:

void some_function(int array[][]) {...}

syntactically, it should not be an error. But, when you try to access array[2][3] now, you can't tell which element is supposed to be accessed. On the other hand, when you have:

void some_function(int array[][5]) {...}

you know that with array[2][3], it can be determined that you are actually accessing element at the memory address *(&array[0][0] + 2*5 + 3) because the function knows the size of the second dimension.

There is one other option, as previously suggested, you can declare a function like:

void some_function(int *array, int cols) { ... }

because this way, you are calling the function with the same "information" as before -- the number of columns. You access the array elements a bit differently then: you have to write *(array + i*cols + j) where you would usually write array[i][j], cause array is now a pointer to integer (not to a pointer).

When you declare a function like this, you have to be careful to call it with the number of columns that are actually declared for the array, not only used. So, for example:

int main(){
   int a[5][5];
   int i, j;

   for (i = 0; i < 3; ++i){
       for (int j=0; j < 3; ++j){
           scanf("%d", &a[i][j]);
       }
   }

   some_function(&a[i][j], 5); // <- correct
   some_function(&a[i][j], 3); // <- wrong

   return 0;
}

Solution 3

C 2018 6.7.6.2 specifies the semantics of array declarators, and paragraph 1 gives constraints for them, including:

The element type shall not be an incomplete or function type.

In a function declaration such as void example(int Array[][]), Array[] is an array declarator. So it must satisfy the constraint that its element type must not be incomplete. Its element type in that declaration is int [], which is incomplete since the size is not specified.

There is no fundamental reason the C standard could not remove that constraint for parameters that are about to be adjusted to pointers. The resulting type int (*Array)[] is a legal declaration, is accepted by compilers, and can be used in the form (*Array)[j].

However, the declaration int Array[][] suggests that Array is at least associated with a two-dimensional array, and hence is to be used in the form Array[i][j]. Even if the declaration int Array[][] were accepted and were adjusted to int (*Array)[], using it as Array[i][j] would not be possible because the subscript operator requires that its pointer operand be a pointer to a complete type, and this requirement is not avoidable as it is needed to calculate the address of the element. Thus, keeping the constraint on the array declarator makes sense, as it is consistent with the intended expression that the argument will be a two-dimensional array, not just a pointer to one one-dimensional array.

Solution 4

Actually whether it is a 2d array or a 1d array, it is stored in the memory in a single line.So to say the compiler where should it break the row indicating the next numbers to be in the next rows we are supposed to provide the column size. And breaking the rows appropriately will give the size of the rows.

Let's see an example:

int a[][3]={ 1,2,3,4,5,6,7,8,9,0 };

This array a is stored in the memory as:

  1  2  3  4  5  6  7  8  9  0

But since we have specified the column size as 3 the memory splits after every 3 numbers.

#include<stdio.h>

int main() {
   int a[][3]={1,2,3,4,5,6},i,j;
   for(i=0;i<2;i++)
   {
       for(j=0;j<3;j++)
       {
           printf("%d  ",a[i][j]);
       }
       printf("\n");
   }

}

OUTPUT:

 1  2  3  
 4  5  6  

In the other case,

int a[3][]={1,2,3,4,5,6,7,8,9,0};

The compiler only knows that there are 3 rows but it doesn't know the number of elements in each row so it cannot allocate memory and will show an error.

#include<stdio.h>

int main() {
   int a[3][]={1,2,3,4,5,6},i,j;
   for(i=0;i<3;i++)
   {
       for(j=0;j<2;j++)
       {
           printf("%d  ",a[i][j]);
       }
       printf("\n");
   }

}

OUTPUT:

 c: In function 'main':
    c:4:8: error: array type has incomplete element type 'int[]'
    int a[3][]={1,2,3,4,5,6},i,j;
        ^
Share:
40,246
jantristanmilan
Author by

jantristanmilan

Updated on February 12, 2020

Comments

  • jantristanmilan
    jantristanmilan over 4 years

    Why can't my parameter be

    void example(int Array[][]){ /*statements*/}
    

    Why do I need to specify the column size of the array? Say for example, 3

    void example(int Array[][3]){/*statements*/}
    

    My professor said its mandatory, but I was coding before school started and I remembered that there was no syntactical or semantic error when I made this my parameter? Or did I miss something?

  • jantristanmilan
    jantristanmilan over 11 years
    thanks man! although it didn't come up when I searched for passing 2d arrays
  • newacct
    newacct over 11 years
    "(note, a pointer array and not its first element)" This does not make sense or is unclear. When an array decays to a pointer, it always becomes a pointer to its first element. The first element of a 2-dimensional array is a 1-dimensional array; and so a 2-dimensional array decays into a pointer to a 1-dimensional array. The type int (*)[3] is the type of a pointer to an array.
  • Maksim Skurydzin
    Maksim Skurydzin over 11 years
    Thanks, you are right, sounds very unclear. I have corrected it to be more comprehensive, I hope.
  • David C. Rankin
    David C. Rankin almost 8 years
    "because arrays are simply pointers" -- you need to be careful making that global statement. char a[x] is not the same as char *b. While the statement may apply to the first level of indirection when an array is passed as a function argument, it does not apply to a in scope -- it is an array.
  • Eugene K
    Eugene K almost 8 years
    @DavidC.Rankin You're right, I edited the phrasing a bit to be more accurate, while still not going too into the details.
  • Lundin
    Lundin over 6 years
    " And in fact that also expands out into anytype (*(*a))" This is still not correct. It would expand to anytype (*a)[] which is a pointer to an array of incomplete type.
  • Lundin
    Lundin over 4 years
    @EricPostpischil Right, I remember getting this mixed up before... it's indeed because you can't declare a incomplete array type. Old comment deleted.
  • Eric Postpischil
    Eric Postpischil about 4 years
    This answer does not give the reason that int Array[][] is not accepted as a parameter declaration. It correctly states that, without the second dimension, Array[i][j] could not be evaluated inside the function, because the length of the second dimension is needed to calculate the location of Array[i][j]. But that does not preclude declaring the parameter this way—we can declare the parameter as int (*Array)[], and this is accepted by a compiler even though we still cannot evaluate Array[i][j] inside the function…
  • Eric Postpischil
    Eric Postpischil about 4 years
  • qwerty_url
    qwerty_url about 3 years
    But doesn't a compiler know the amount of columns an array has? When I try this: #define COL 5 int an_array[][COL] = { {100, 200, 300, 400, 500}, {600, 700, 800, 900, 1000} }; int test(int (*arr)[1]) {return 0;} int main() { test(an_array); } I get an error telling me that the amount of columns of the array that I passed to the function doesn't match the amount of columns specified in the declaration of test().