When programming in C (or C++) there are three different ways to specify the parameter in a function that takes an array.
Here is an example (implementing std::accumulate
from C++ in C) that shows you what I mean.
I can write it like this:
int accumulate(int n, int *array)
{
int i;
int sum = 0;
for (i = 0; i < n; ++i) {
sum += array[i];
}
return sum;
}
This can also be written to this (which means the very same thing):
int accumulate(int n, int array[])
{
int i;
int sum = 0;
for (i = 0; i < n; ++i) {
sum += array[i];
}
return sum;
}
I can also write it like this:
int accumulate(int n, int (*array)[])
{
int i;
int sum = 0;
for (i = 0; i < n; ++i) {
sum += (*array)[i];
}
return sum;
}
All these options are very similar and generate the same executable code but they have a slight difference which is how the caller passes the arguments.
This is how the first two options gets called:
int main(void)
{
int a[] = {3, 4, 2, 4, 6, 1, -40, 23, 35};
printf("%dn", accumulate(ARRAY_LENGTH(a), a));
return 0;
}
This is how the thrid option gets called:
int main(void)
{
int a[] = {3, 4, 2, 4, 6, 1, -40, 23, 35};
printf("%dn", accumulate(ARRAY_LENGTH(a), &a));
return 0;
}
Note that the third option requires to user to explicitly specify the address of a
with &a
. The first two options does not require this because arrays implicitly gets converted into pointers to the same type in C.
I have always preferred the third approach.
This is why:
-
It is more consistent with how other types are passed by pointers.
int draw_point(struct point *p); int main() { struct point p = {3, 4}; draw_point(&p); // Here is the 'address of' operator required. }
-
It makes it possible to use macros like
ARRAY_LENGTH
to get the amount of elements in the array.#include <stdio.h> #define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0])) void this_works(int (*array)[10]) { /* This works! */ printf("%dn", ARRAY_LENGTH(*array)); } void this_is_invalid_and_dangerous(int array[10]) { /* This does NOT work because `array` is actually a pointer. */ printf("%dn", ARRAY_LENGTH(array)); }
The only advantage I see with int array[]
(and int *array
) is that you get to write array[X]
instead of (*array)[X]
when you wish to grab an index.
What are your professional thoughts on this? Which approach do you think is better and why? Do you ever mix the two? If so when do you choose what?
Like I said have I always preferred using int (*array)[N]
but I see that the other two approaches are quite common as well.
4
In practice, you’ll see
int accumulate( int n, int *array)
most often. It’s the most flexible (it can handle arrays of different sizes) and most closely reflects what’s happening under the hood.
You won’t see
int accumulate( int (*array)[N] )
as often, since it assumes a specific array size (the size must be specified).
If your compiler supports variable-length array syntax, you could do
int accumulate( size_t n, int (*array)[n] )
but I don’t know how common that is.
7
In C, when the array notation is used for a function parameter, it is automatically transformed into a pointer declaration, so declaring parameter as int* array and int array[] are equivalent. I tend to use second one because it is more clear that function expects an array as an argument.
Declaring function parameter as int (*array)[] is not equivalent to previous two, it is transformed to int** and it should be used when value of the parameter itself could be changed in function, i.e. reallocation of the array.
4
You say that there are 3 ways in C and C++, but C++ actually makes a fourth available:
template<std::size_t n>
void arrayFunction(std::array<int,n> &array)
{ ...}
This has several advantages over the solutions you suggest:
- The parameter for the size of the array will be automatically determined on use by the compiler, meaning you don’t have to specify it
- The size is a compile time constant, meaning that the compiler can perform many optimizations that are impossible for the versions you suggest
- It is impossible to specify an incorrect size, as the size is part of the type of the array
The first declaration also allows you write the function differently:
int accumulate(int n, int *array)
{
int sum = 0;
while (n-- > 0) sum += *array++;
return sum;
}
so you don’t need the variable i.
Whatever’s idiomatic to the code base should be preferred, followed by whatever’s the easiest to understand, followed at some distance by whatever’s the most performant. The second declaration is the easiest to understand.