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

7

46

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?

Apical answered 10/10, 2012 at 6:46 Comment(5)
It is needed to compute the relative offset of the item you're actually accessing. The array itself is just a linear block, but rather than you having to to do (row*colwidth + col) (which works, btw) the offsets are done for you by the compiler once it knows the second-order magnitude.Barilla
but if I don't declare the offset(the col size) what would the compiler set as default?Apical
Either you didn't use 2+d arrays back then or it wasn't C.Sessile
Why is it allowed to omit the first dimension, but not the other dimensions when declaring a multi-dimensional array?Javierjavler
Just ask yourself how you would possibly go about converting an abstraction that is the "2D array" to a 1D grid of memory locations (the actuality beneath the abstraction) if you did not know the column size...Embellish
M
51

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?

Mooncalf answered 10/10, 2012 at 6:59 Comment(3)
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…Marilumarilyn
The actual reason is a rule in the standard that the element type for an array declarator must be a complete type.Marilumarilyn
This does not answer why it worked before. I have a project from year 2003, which was compiled fine with gcc version from that time, but doesn't compile with today's gcc due to this error.Luisluisa
A
16

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;
}
Attah answered 10/10, 2012 at 7:19 Comment(0)
M
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.

Marilumarilyn answered 11/2, 2020 at 17:38 Comment(0)
K
1

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;
        ^
Kirkpatrick answered 28/9, 2017 at 4:7 Comment(0)
N
0

There is a similar post regarding this. You can refer below link. Creating Array in C and passing pointer to said array to function Hope it helps.

On the other hand, compiler needs to the second dimension so that it can move "Array" from one pointer to next since the whole memory is arranged in a linear fashion

Northwestward answered 10/10, 2012 at 6:57 Comment(0)
E
0

I thought this was a cool approach. If you take this as the formula to calculate the address of an element in the array:

a[i][j] = baseArrayAddress + (i + (colSize + elementSize)) + (j * (elementSize))

Then you can see that the only thing the compiler needs to know (which it can't otherwise infer) is the size of the column, thus you need to provide it as the programmer so the algorithm can run to calculate the offset.

The row number only acts as a multiplier and is provided by the programmer when trying to dereference an array location.

Embellish answered 22/1, 2023 at 21:50 Comment(0)
N
-2

When you create a 2D array, anytype a[3][4], in memory what you actually create is 3 contiguous blocks of 4 anytype objects.

a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]

Now the next question is, why is that so? Because, keeping with the spec and structure of the language, anytype a[3][4] actually expands out into anytype (*a)[4], because arrays decay into pointers. And in fact that also expands out into anytype (*(*a)), however, you've now completely lost the size of the 2D array. So, you must help the compiler out a bit.

If you ask the program for a[2], the program can follow the exact same steps that it does for 1D arrays. It simply can return the 3rd element of sizeof(object pointed to), the object pointed to here is of size 4 anytype objects.

Noli answered 21/7, 2016 at 5:8 Comment(1)
" 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.Euhemerus

© 2022 - 2024 — McMap. All rights reserved.