5

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("%d\n", 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("%d\n", 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("%d\n", ARRAY_LENGTH(*array));
    }
    
    void this_is_invalid_and_dangerous(int array[10])
    {
        /* This does NOT work because `array` is actually a pointer. */
        printf("%d\n", 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.

  • “Both versions are very similar and generate the same executable code” – it seems that one version receives an *int while the other gets an **int, so I doubt they'll compile to the same instructions. Furthermore, some of your examples use pointers, others arrays without explicit lengths as in (*array)[], and others provide a static length: (*array)[10], and I'd think all of those are different. To answer what manner of passing arrays is safe and sensible, you should also state what C standard you are following, and whether you're allowing compiler-specific extensions. – amon Jan 10 '15 at 14:29
  • Try compiling size_t function(int (*a)[]){return sizeof(*a);}. To me it fails to compile using gcc -std=c99 and prints this error message: main.c: In function ‘function’: main.c:6:18: error: invalid application of ‘sizeof’ to incomplete type ‘int[]’ This suggests that *a has the type int[] and not int *. – wefwefa3 Jan 10 '15 at 15:44
  • I do not have access to the standard documents myself but I assume that gcc -std=c99 -pedantic follows the C99 standard in each and every point. – wefwefa3 Jan 10 '15 at 15:55
  • I do not think that I am "good enough" to make these assumptions so I made a question on stackoverflow here: <stackoverflow.com/questions/27878583/…>. Hopefully some smart guy with a copy of the standard documents can answer the question. – wefwefa3 Jan 10 '15 at 16:51
7

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.

  • It seems like int (*)[] (with unspecified size) is legal in C but illegal in C++. It compiles without problems gcc -std=c89 -pedantic but fails to compile using g++. – wefwefa3 Jan 12 '15 at 7:46
  • I made a question on Stackoverflow where I asked about it (int (*p)[]) here: <stackoverflow.com/questions/27897646/…> hopefully it will get some clarifying answers. – wefwefa3 Jan 12 '15 at 8:18
  • 1
    @elias: int (*a)[] is an incomplete type; it's legal as long as you don't try to do anything that requires the size of the type to be known. For example, you couldn't use sizeof *a to get the size of the array in that case. Also, remember that a pointer to an N-element array is a different type from a pointer to an M-element array; they won't be interchangeable. As a matter of safety, you always want to specify the size of the array parameter. – John Bode Jan 12 '15 at 12:21
  • int arr[5]={0};int (*p)[5] = &arr Why *p is the address of the array?I know *p is int [5] type, but p stores the address of array. Since dereference p, I thought it will be 0, the value of arr[0]. Coz that's what stores in the address indicated by p. – MMMMMCCLXXVII Nov 21 '17 at 15:28
  • @MMMMMCCLXXVII: *p is not the address of the array - it's the array object itself (p == &arr, so *p == arr). Anywhere you'd write arr, write (*p) instead - *arr == *(*p), arr[i] == (*p)[i], etc. – John Bode Nov 21 '17 at 15:39
3

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.

  • int function(int (*a)[]){printf("%d\n", sizeof(*a));} fails to compile and prints this error message (using gcc -std=c99): main.c:6:26: error: invalid application of ‘sizeof’ to incomplete type ‘int[]’ which shows that *a has the type int[] and not int *. – wefwefa3 Jan 10 '15 at 15:38
  • 1
    @elias Yes, int (*array)[10] means that array is a pointer to an array of 10 integers, which is semantically the same as int**, the only difference is that it gives possibly false and dangerous idea of the array size. Try passing array of different size and see if it still prints the correct value. Btw, note that there is no [] in C after compile time, it all eventually decays to pointers. – vasicbre Jan 10 '15 at 16:12
  • I have become very confused about. I feel that this is something that the people and stackoverflow should answer so I made a question there: <stackoverflow.com/questions/27878583/…> – wefwefa3 Jan 10 '15 at 16:48
  • 1
    Good idea, I see a lot of clarifying answers for me as well. Thanks. – vasicbre Jan 10 '15 at 17:12
2

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
2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.