Why do we need to specify the column size when passing a 2D array as a parameter?
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;
^
jantristanmilan
Updated on February 12, 2020Comments
-
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 over 11 yearsthanks man! although it didn't come up when I searched for passing 2d arrays
-
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 over 11 yearsThanks, you are right, sounds very unclear. I have corrected it to be more comprehensive, I hope.
-
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 aschar *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 toa
in scope -- it is an array. -
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 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 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 about 4 yearsThis 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 ofArray[i][j]
. But that does not preclude declaring the parameter this way—we can declare the parameter asint (*Array)[]
, and this is accepted by a compiler even though we still cannot evaluateArray[i][j]
inside the function… -
Eric Postpischil about 4 years
-
qwerty_url about 3 yearsBut 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().