First, the array passed to the function actually passes a pointer to the first element of the array, for example, if you have
int a[] = { 1, 2, 3 }; f(a);
Then f() gets &a[0] passed to it. So, when writing prototypes of functions, the following equivalents:
void f(int arr[]); void f(int *arr);
This means that the size of the array is lost, and f() , in general, cannot determine the size. (That's why I prefer void f(int *arr) form over void f(int arr[]) .)
There are two cases when f() does not need information, and in these two cases, it does not have an additional parameter.
Firstly, arr has a special, consistent value, which both the caller and f() mean "end". For example, you might agree that a value of 0 means "Finish."
Then one could write:
int a[] = { 1, 2, 3, 0 }; int result = f(a);
and define f() something like:
int f(int *a) { size_t i; int result = 0; for (i=0; a[i]; ++i) result += a[i]; return result; }
Obviously, the above pattern only works if both the caller and the callee agree and follow the agreement. An example is the strlen() function in the C library. It calculates the length of a string by finding 0 . If you give him something that does not have 0 at the end, all bets are disabled and you are in the territory of undefined behavior.
The second case is when you really don't have an array. In this case, f() takes a pointer to an object ( int in your example). So:
int change_me = 10; f(&change_me); printf("%d\n", change_me);
with
void f(int *a) { *a = 42; }
okay: f() doesn't work in the array anyway.