Passing an array as an argument to a function in C

254,949

Solution 1

When passing an array as a parameter, this

void arraytest(int a[])

means exactly the same as

void arraytest(int *a)

so you are modifying the values in main.

For historical reasons, arrays are not first class citizens and cannot be passed by value.

Solution 2

For passing 2D (or higher multidimensional) arrays instead, see my other answer here: How to pass a multidimensional array to a function in C and C++

Passing 1D arrays as function parameters in C (and C++)

1. Standard array usage in C with natural type decay (adjustment) from array to ptr

@Bo Persson correctly states in his great answer here:

When passing an array as a parameter, this

void arraytest(int a[])

means exactly the same as

void arraytest(int *a)

Let me add some comments to add clarity to those two code snippets:

// param is array of ints; the arg passed automatically "adjusts" (frequently said
// informally as "decays") from `int []` (array of ints) to `int *` 
// (ptr to int)
void arraytest(int a[])

// ptr to int
void arraytest(int *a)

However, let me add also that the above two forms also:

  1. mean exactly the same as

     // array of 0 ints; automatically adjusts (decays) from `int [0]`
     // (array of zero ints) to `int *` (ptr to int)
     void arraytest(int a[0])
    
  2. which means exactly the same as

     // array of 1 int; automatically adjusts (decays) from `int [1]`
     // (array of 1 int) to `int *` (ptr to int)
     void arraytest(int a[1])
    
  3. which means exactly the same as

     // array of 2 ints; automatically adjusts (decays) from `int [2]`
     // (array of 2 ints) to `int *` (ptr to int)
     void arraytest(int a[2])
    
  4. which means exactly the same as

     // array of 1000 ints; automatically adjusts (decays) from `int [1000]`
     // (array of 1000 ints) to `int *` (ptr to int)
     void arraytest(int a[1000])
    
  5. etc.

In every single one of the array examples above, and as shown in the example calls in the code just below, the input parameter type adjusts (decays) to an int *, and can be called with no warnings and no errors, even with build options -Wall -Wextra -Werror turned on (see my repo here for details on these 3 build options), like this:

int array1[2];
int * array2 = array1;

// works fine because `array1` automatically decays from an array type
// to a pointer type: `int *`
arraytest(array1);
// works fine because `array2` is already an `int *` 
arraytest(array2);

As a matter of fact, the "size" value ([0], [1], [2], [1000], etc.) inside the array parameter here is apparently just for aesthetic/self-documentation purposes, and can be any positive integer (size_t type I think) you want!

In practice, however, you should use it to specify the minimum size of the array you expect the function to receive, so that when writing code it's easy for you to track and verify. The MISRA-C-2012 standard (buy/download the 236-pg 2012-version PDF of the standard for £15.00 here) goes so far as to state (emphasis added):

Rule 17.5 The function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elements.

...

If a parameter is declared as an array with a specified size, the corresponding argument in each function call should point into an object that has at least as many elements as the array.

...

The use of an array declarator for a function parameter specifies the function interface more clearly than using a pointer. The minimum number of elements expected by the function is explicitly stated, whereas this is not possible with a pointer.

In other words, they recommend using the explicit size format, even though the C standard technically doesn't enforce it--it at least helps clarify to you as a developer, and to others using the code, what size array the function is expecting you to pass in.


2. Forcing type safety on arrays in C

(Not recommended (correction: sometimes recommended, especially for fixed-size multi-dimensional arrays), but possible. See my brief argument against doing this at the end. Also, for my multi-dimensional-array [ex: 2D array] version of this, see my answer here.)

As @Winger Sendon points out in a comment below my answer, we can force C to treat an array type to be different based on the array size!

First, you must recognize that in my example just above, using the int array1[2]; like this: arraytest(array1); causes array1 to automatically decay into an int *. HOWEVER, if you take the address of array1 instead and call arraytest(&array1), you get completely different behavior! Now, it does NOT decay into an int *! This is because if you take the address of an array then you already have a pointer type, and pointer types do NOT adjust to other pointer types. Only array types adjust to pointer types. So instead, the type of &array1 is int (*)[2], which means "pointer to an array of size 2 of int", or "pointer to an array of size 2 of type int", or said also as "pointer to an array of 2 ints". So, you can FORCE C to check for type safety on an array by passing explicit pointers to arrays, like this:

// `a` is of type `int (*)[2]`, which means "pointer to array of 2 ints"; 
// since it is already a ptr, it can NOT automatically decay further
// to any other type of ptr 
void arraytest(int (*a)[2])
{
    // my function here
}

This syntax is hard to read, but similar to that of a function pointer. The online tool, cdecl, tells us that int (*a)[2] means: "declare a as pointer to array 2 of int" (pointer to array of 2 ints). Do NOT confuse this with the version withOUT parenthesis: int * a[2], which means: "declare a as array 2 of pointer to int" (AKA: array of 2 pointers to int, AKA: array of 2 int*s).

Now, this function REQUIRES you to call it with the address operator (&) like this, using as an input parameter a POINTER TO AN ARRAY OF THE CORRECT SIZE!:

int array1[2];

// ok, since the type of `array1` is `int (*)[2]` (ptr to array of 
// 2 ints)
arraytest(&array1); // you must use the & operator here to prevent
                    // `array1` from otherwise automatically decaying
                    // into `int *`, which is the WRONG input type here!

This, however, will produce a warning:

int array1[2];

// WARNING! Wrong type since the type of `array1` decays to `int *`:
//      main.c:32:15: warning: passing argument 1 of ‘arraytest’ from 
//      incompatible pointer type [-Wincompatible-pointer-types]                                                            
//      main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
arraytest(array1); // (missing & operator)

You may test this code here.

To force the C compiler to turn this warning into an error, so that you MUST always call arraytest(&array1); using only an input array of the corrrect size and type (int array1[2]; in this case), add -Werror to your build options. If running the test code above on onlinegdb.com, do this by clicking the gear icon in the top-right and click on "Extra Compiler Flags" to type this option in. Now, this warning:

main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types]                                                            
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’    

will turn into this build error:

main.c: In function ‘main’:
main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types]
     arraytest(array1); // warning!
               ^~~~~~
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
 void arraytest(int (*a)[2])
      ^~~~~~~~~
cc1: all warnings being treated as errors

Note that you can also create "type safe" pointers to arrays of a given size, like this:

int array[2]; // variable `array` is of type `int [2]`, or "array of 2 ints"

// `array_p` is a "type safe" ptr to array of size 2 of int; ie: its type
// is `int (*)[2]`, which can also be stated: "ptr to array of 2 ints"
int (*array_p)[2] = &array;

...but I do NOT necessarily recommend this (using these "type safe" arrays in C), as it reminds me a lot of the C++ antics used to force type safety everywhere, at the exceptionally high cost of language syntax complexity, verbosity, and difficulty architecting code, and which I dislike and have ranted about many times before (ex: see "My Thoughts on C++" here).


For additional tests and experimentation, see also the link just below.

References

See links above. Also:

  1. My code experimentation online: https://onlinegdb.com/B1RsrBDFD

See also:

  1. My answer on multi-dimensional arrays (ex: 2D arrays) which expounds upon the above, and uses the "type safety" approach for multi-dimensional arrays where it makes sense: How to pass a multidimensional array to a function in C and C++

Solution 3

If you want to pass a single-dimension array as an argument in a function, you would have to declare a formal parameter in one of following three ways and all three declaration methods produce similar results because each tells the compiler that an integer pointer is going to be received.

int func(int arr[], ...){
    .
    .
    .
}

int func(int arr[SIZE], ...){
    .
    .
    .
}

int func(int* arr, ...){
    .
    .
    .
}

So, you are modifying the original values.

Thanks !!!

Solution 4

You are not passing the array as copy. It is only a pointer pointing to the address where the first element of the array is in memory.

Solution 5

Passing a multidimensional array as argument to a function. Passing an one dim array as argument is more or less trivial. Let's take a look on more interesting case of passing a 2 dim array. In C you can't use a pointer to pointer construct (int **) instead of 2 dim array. Let's make an example:

void assignZeros(int(*arr)[5], const int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 5; j++) {
            *(*(arr + i) + j) = 0;
            // or equivalent assignment
            arr[i][j] = 0;
        }
    }

Here I have specified a function that takes as first argument a pointer to an array of 5 integers. I can pass as argument any 2 dim array that has 5 columns:

int arr1[1][5]
int arr1[2][5]
...
int arr1[20][5]
...

You may come to an idea to define a more general function that can accept any 2 dim array and change the function signature as follows:

void assignZeros(int ** arr, const int rows, const int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            *(*(arr + i) + j) = 0;
        }
    }
}

This code would compile but you will get a runtime error when trying to assign the values in the same way as in the first function. So in C a multidimensional arrays are not the same as pointers to pointers ... to pointers. An int(*arr)[5] is a pointer to array of 5 elements, an int(*arr)[6] is a pointer to array of 6 elements, and they are a pointers to different types!

Well, how to define functions arguments for higher dimensions? Simple, we just follow the pattern! Here is the same function adjusted to take an array of 3 dimensions:

void assignZeros2(int(*arr)[4][5], const int dim1, const int dim2, const int dim3) {
    for (int i = 0; i < dim1; i++) {
        for (int j = 0; j < dim2; j++) {
            for (int k = 0; k < dim3; k++) {
                *(*(*(arr + i) + j) + k) = 0;
                // or equivalent assignment
                arr[i][j][k] = 0;
            }
        }
    }
}

How you would expect, it can take as argument any 3 dim arrays that have in the second dimensions 4 elements and in the third dimension 5 elements. Anything like this would be OK:

arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...

But we have to specify all dimensions sizes up to the first one.

Share:
254,949

Related videos on Youtube

Mohan Mahajan
Author by

Mohan Mahajan

I am always been interested in automating process, programming. How the process can be automated. This developed my interest in programming. I am a Computer Engineer eager to master the programming languages.

Updated on July 08, 2022

Comments

  • Mohan Mahajan
    Mohan Mahajan almost 2 years

    I wrote a function containing array as argument, and call it by passing value of array as follows.

    void arraytest(int a[])
    {
        // changed the array a
        a[0] = a[0] + a[1];
        a[1] = a[0] - a[1];
        a[0] = a[0] - a[1];
    }
    
    void main()
    {
        int arr[] = {1, 2};
        printf("%d \t %d", arr[0], arr[1]);
        arraytest(arr);
        printf("\n After calling fun arr contains: %d\t %d", arr[0], arr[1]);
    }
    

    What I found is though I am calling arraytest() function by passing values, the original copy of int arr[] is changed.

    Can you please explain why?

  • ShinTakezou
    ShinTakezou almost 13 years
    I suppose there are () missing for *a+1 should be *(a+1)
  • user3167101
    user3167101 almost 13 years
    @Shin Thanks, been a while since I've played with C.
  • Heberto Mayorquin
    Heberto Mayorquin over 8 years
    Which notation is better under which circumstances?
  • Bo Persson
    Bo Persson over 8 years
    @Ramon - I would use the second option, as it seems less confusing and better indicates that you don't get a copy of the array.
  • Suraj Jain
    Suraj Jain over 7 years
    If I pass an array to a function . Say for example I made an array int a[10] and assigned each element random value . Now if I pass this array to a function using int y[] or int y[10] or int *y .And then in that function I use sizeof(y) Answer will be the bytes pointer has been allocated. So in this case ,it will decay as a pointer , It Would Be Helpfull If You include this too. See this postimg.org/image/prhleuezd
  • Suraj Jain
    Suraj Jain over 7 years
    If I use sizeof operate in the function in the originally we defined array then it will decay as an array , but if I pass in other function then there use sizeof operator it will decay as a pointer.
  • Suraj Jain
    Suraj Jain over 7 years
  • Jacquelyn.Marquardt
    Jacquelyn.Marquardt over 7 years
    Can you explain the "historical reasons"? I suppose passing by values would need a copy and so a waste of memory.. thanks
  • Bo Persson
    Bo Persson over 7 years
    @lucapozzobon - Originally C didn't have any pass by value, except for single values. It wasn't until struct was added to the language that this was changed. And then it was considered too late to change the rules for arrays. There were already 10's of users. :-)
  • Gabriel Staples
    Gabriel Staples almost 6 years
    ...means exactly the same as void arraytest(int a[1000]) etc etc. Expanded answer here: stackoverflow.com/a/51527502/4561887.
  • wingerse
    wingerse almost 6 years
    void arraytest(int (*a)[1000]) is better because then the compiler will error if size is wrong.
  • Aviv Cohn
    Aviv Cohn about 4 years
    I know this is old. Two questions if anybody happens to see this :) 1. @ThomSmith wrote that &a is not quite the same as &a[0] when a is an array. How so? In my test program, both show to be the same, both in the function where the array is declared, and when passed into a different function. 2. The writer writes that "char b[] = "foo" is not the same as char b[] = &("foo")". For me, the latter doesn't even compile. Is it just me?
  • Puck
    Puck almost 4 years
    I was looking for your second example, can you elaborate what's the advantages of each methods ?
  • Gabriel Staples
    Gabriel Staples over 3 years
    I'm upvoting this. I'm not sure why it's downvoted.
  • Gabriel Staples
    Gabriel Staples over 3 years
    @WingerSendon, I knew there were some subtleties I needed to verify here, and that syntax is confusing (like a function ptr syntax is confusing), so I took my time and have finally updated my answer with a large new section titled Forcing type safety on arrays in C, covering your point.
  • daryooosh
    daryooosh over 3 years
    @GabrielStaples,Thanks. Your answer is very helpful. Can you refer me a reference to learn advanced c in this way?
  • Gabriel Staples
    Gabriel Staples over 3 years
    @daryooosh, unfortunately, I can't. I don't have any great references. I've picked this up a little here, a little there, by digging deep over many years. The best I can do is tell you I occasionally drop some of what I learn like this into my eRCaGuy_hello_world repo here. Keep in mind though that the C type safety stuff I used above should be used VERY sparingly. It will complicate your code and decrease readability a ton, and isn't worth it. Focus on simple syntax where able, and making things readable.
  • Gabriel Staples
    Gabriel Staples over 3 years
    Note also the canonical classical C textbook is this K&R The C Programming Language book: en.wikipedia.org/wiki/The_C_Programming_Language.
  • Gabriel Staples
    Gabriel Staples over 3 years
    @daryooosh, also, I've gotten into the habit of putting what I learn onto Stack Overflow. Now, whenever I forget something, I google my name plus some topic. Ex: "gabriel staples c array parameters" leads to this very answer. Now, when I forget how to do this stuff, I literally google my name and come right back to my own answer here. I can't remember this stuff. So, I write it down.
  • Gabriel Staples
    Gabriel Staples over 3 years
    @daryooosh, one more place I write stuff down: eRCaGuy_dotfiles/git & Linux cmds, help, tips & tricks - Gabriel.txt. "Document what you know when you know it."
  • Binarus
    Binarus over 3 years
    @GabrielStaples I am not the one who downvoted, but perhaps it happened because "passed by reference" is a very ambiguous (not to say wrong) term here. A reference is a thing which only exists in C++ and means the opposite there (i.e. changes made to a non-pointer reference parameter in a function are reflected outside the function). Therefore, we really shouldn't use the term reference when we are talking about pointers or decay to pointers in standard C.
  • Gabriel Staples
    Gabriel Staples almost 3 years
    @daryooosh, you wanna see something cool? Check this out: dynamically inject your own function call before or after another program's main() func. This is yet another discovery. It's a long and slow process; I know of no quick ways to gain this knowledge, but by trying to solve tough problems, then doing tough study when you can't, and then writing great examples like like this the very first time you learn it, so you never forget where to look to learn it again later.