Passing Arrays as Parameter in C

Short answer: you can’t but yes, you can!

This is an endlessly recurring question in many groups on the Internet. Today (2016/01/14) Google has 171 Million hits. This article answers the question and sheds some light on arrays in C. In the second part below I’ll discuss two-dimensional arrays as well.array_asm

IMO, arrays are the most misunderstood language construction of C, even more than pointers. Everybody knows that these mysterious pointers are somehow dangerous, thus people (beginners) tend to either avoid, or really try to understand them.

But arrays are something that every beginner quickly understands (in any language). But in C, arrays are actually hidden pointers which makes them even more dangerous than pointers.

Beginners may also read my articles about learning C part I, part II, and part III.

Passing Arguments

Before we continue we should examine what exactly means passing an argument? Look at the following trivial code example.

void do_something(int a)
{
   for (; a > -1; a--)
   {
      /* do something .... */
   }
   printf("%d\n", a);
}

int main(int argc, char **argv)
{
   int a = 1;

   do_something(a);
   printf("%d\n", a);
   return 0;
}

It should be obvious that the program outputs -1 and 1.

How arguments are passed to functions is different between languages. Generally, there are two methods: call-by-value and call-by-reference. C (and C++, as many others) strictly implement call-by-value. This means that the language always passes a copy of a variable to the function. Thus in the example above, the modification of variable a in do_something() does not influence the variable a in main(). But as soon as we try to pass an array to the function it seems to behave different. Look at the following code example.

void do_something(int arr[])
{
   for (int i = 0; arr[i] != -1; i++)
   {
      /* do something .... */
      arr[i] = i;
   }
   printf("%d\n", arr[0]);
}

int main(int argc, char **argv)
{
   int arr[] = {5, 4, 3, 2, 1, 0, -1};

   do_something(arr);
   printf("%d\n", arr[0]);
   return 0;
}

The program will output 0 and 0. And not — as probably expected — 0 and 5! The reason is that C handles the array as a pointer. This means that the variable arr (internally) actually is a pointer. Thus it passes a pointer to the array to the function. And because of that the following declarations of do_something() are absolutely equal and indifferent:

void do_something(int arr[]);
void do_something(int arr[7]);
void do_something(int *arr);

You can verify this by looking at the disassembly. Gcc will emit the same code for all three variants.

If we now try to define arr in main() as explicit pointer, the compiler will emit the same code as if we define it as array with the only slight difference, that it explicitly creates the variable arr on the stack. This is clear and can be observed in the disassembly.

void do_something(int *arr)
{
   for (int i = 0; arr[i] != -1; i++)
   {
      /* do something .... */
      arr[i] = i;
   }
   printf("%d\n", arr[0]);
}

int main(int argc, char **argv)
{
   int *arr = (int[]) {5, 4, 3, 2, 1, 0, -1};

   do_something(arr);
   printf("%d\n", arr[0]);
   return 0;
}

Passing The Array’s Content

»But I want to force it to pass the full array (its content) like a variable and not just a pointer! How can I do that?«

I honestly encourage you to rethink your design if you try to do this. It is typically an indicator for a highly unusual and impractical code design. Specifically, if you deal with “large” arrays…let’s say 8 or more elements.

But if you still need to do this, the solution is to wrap the array into a structure. Look at the following listing:

struct array { int arr[7]; };

void do_something(struct array a)
{
   for (int i = 0; a.arr[i] != -1; i++)
   {
      /* do something .... */
      a.arr[i] = i;
   }
   printf("%d\n", a.arr[0]);
}

int main(int argc, char **argv)
{
   struct array a = {{5, 4, 3, 2, 1, 0, -1}};

   do_something(a);
   printf("%d\n", a.arr[0]);
   return 0;
}

Variable a is of type struct array. There is nothing special about structures.They are handled as regular variables, thus it passes a copy of it to the function because of the call-by-value concept.

Again, you should avoid such constructions, specifically for large arrays. Because of the copy process it will definitely slow down your program and increase its memory (stack) consumption.

What About Two-dimensional Arrays?

If you think you understood everything above we start to discuss two-dimensional arrays. If an array is internally handled as a pointer than the same should work for two-dimensional arrays as well. But if we try to define the function with the prototype void do_something(int arr[][]) similar to what we would do with a one-dimensional array, the compiler will immediately emit the error message “error: array type has incomplete element type“.

If we try to fool the compiler and define it directly as a pointer with the prototype void do_something(int *arr) it will show even more error messages. at the function call in main it will say “note: expected ‘int *’ but argument is of type ‘int (*)[3]’“, and at the access to an array element in do_something() it says “error: subscripted value is neither array nor pointer nor vector“.

A two-dimensional array looks like a structure which has rows and columns. But memory is just a linear one-dimensional space of bytes. There is no second dimension. Thus the array has to be “flattened” into a one-dimensional structure. The compiler does this by simply storing in turn one row after the other. To find a specific element within this memory, it does a simple calculation:

  1. bytes_per_element = sizeof(type_of_element)
  2. bytes_per_row = #total_columns * bytes_per_element
  3. element_address = #row * bytes_per_row + #column * bytes_per_element

And this calculation does only work if the number of columns is known in advance. Thus, the compiler demands you to specify at least the number of columns! Otherwise it isn’t able to calculate the memory location of a specific element. This in turn is the reason for the error messages which I mentioned above. In both cases. It is neither possible to declare a two-dimensional array without giving the number of at least one dimension, nor is it possible to access a two-dimensional array with two index operaters through a pointer.

In case you have a pointer to a two-dimensional array, you have to calculate the memory address yourself using pointer arithmetic and then dereference it, e.g. *(ptr + y * elements_per_row + x).

The following listing shows a 2D-array function call.

void do_something(int arr[][3])
{
   for (int i = 0; arr[i][0] != -1; i++)
   {
      /* do something .... */
      arr[i][0] = i;
   }
   printf("%d\n", arr[0][0]);
}

int main(int argc, char **argv)
{
   int arr[][3] = {{11, 12, 13}, {21, 22, 23}, {-1, -1, -1}};

   do_something(arr);
   printf("%d\n", arr[0][0]);
   return 0;
}

 Any questions?

Comments

Passing Arrays as Parameter in C — 4 Comments