Parameters and Arguments
Two words that get used interchangeably but are worth separating:
- A parameter is the variable in the function's definition -
int ninint square(int n). - An argument is the value you supply at the call site - the
4insquare(4).
Calling a function creates its parameters as fresh local variables and copies the arguments into them. That copy is the whole subject of this page.
C Passes By Value - Always
This is the rule, and C has no exception to it: a function receives copies of its arguments. Assigning to a parameter changes only the copy.
value is still 42. The function got a copy called x, set the copy to zero, and the copy vanished when the function returned. Renaming the parameter to value changes nothing - the name does not connect them; they are two different variables.
The same is true of every type: int, double, char, a pointer, even a whole struct (copied field by field). If you come from Python, Java, or JavaScript, note that those languages pass object references by value, so mutating an object inside a function is visible outside. C does not have that - it copies the object itself.
Passing by value is a feature, not a limitation. A function cannot corrupt the caller's data by accident, which makes it far easier to reason about.
The Classic Failure: swap
The textbook demonstration is a function that tries to exchange two values:
The logic is correct - it really does swap a and b. But a and b are copies, so the work is thrown away the instant the function returns.
Simulating Pass By Reference With Pointers
To let a function change a caller's variable, give it the variable's address instead of its value. That address is itself passed by value - the pointer is copied - but the copy still points at the original object, so writing through it reaches the caller's memory.
Three pieces of syntax carry the whole idea:
int *ain the parameter list declaresaas "a pointer to anint".&xat the call site produces the address ofx.*ainside the function means "theintthatapoints at" - readable and assignable.
This is what C programmers mean by "pass by reference", and it is worth being precise: it is still pass by value. The pointer is copied. Assigning to a itself (a = NULL;) would change only the copy; assigning to *a changes the caller's variable. Pointers covers the model in full.
The cost is that a call site no longer tells you whether a variable can change - which is exactly why & is required. swap(x, y) will not compile; you have to write swap(&x, &y), and that & is the visible marker that this call may modify x.
Returning Several Results
return produces one value, so extra outputs travel through pointer parameters. The common shape is a status code as the return value and the real results written through pointers:
Note that q and r keep their previous values when the call fails - the function returned before writing anything. That is the contract the caller has to respect, and it is why the status check comes before using the outputs.
The alternative is to return a struct holding both fields, which avoids pointers entirely and is often the nicer design when the values genuinely belong together.
Arrays Are Different
Arrays do not follow the copy rule, and this trips up almost everyone. When you pass an array, it decays to a pointer to its first element - so the function gets an address, not a copy of the data.
The caller's array really changed, with no & anywhere - because data already is an address in this context. Three consequences:
int a[] and int *a mean exactly the same thing in a parameter list. Even int a[100] does; the size is ignored. Writing int a[] documents the intent, but do not read it as "an array is being copied".
sizeof inside the function is wrong. This is the bug:
In main, sizeof(data) is 20 bytes and the length comes out as 5. Inside the function sizeof(a) is the size of a pointer - 8 on a 64-bit system - so the "length" is 2. The array's size simply is not available there. Always pass the length as a separate parameter. Modern compilers warn about this one with -Wall.
Use const when you are not going to write. const int a[] says the function only reads, which the compiler enforces and a reader can rely on:
int sum(const int a[], int n); /* promises not to modify the array */
Strings are arrays too, so the same applies: void greet(const char *name) is the standard signature for a function that reads a string without changing it.
What About the Cost?
Passing a large struct by value copies every byte of it on every call. For a struct with a handful of fields that is fine and often faster than the indirection a pointer adds. For a big one, pass a pointer - and mark it const if the function only reads:
struct Config { char name[64]; int flags[32]; double weights[128]; };
void applyConfig(const struct Config *cfg); /* no 512-byte copy per call */
const struct Config *cfg means "a pointer to a Config I will not modify", which gets the efficiency of a pointer with the safety of a copy.
Common Mistakes
- Expecting a plain parameter to change the caller's variable. It cannot. Pass a pointer.
- Forgetting
&at the call site.swap(x, y)whereswapwants pointers is a type error - the compiler catches it, but only if a prototype is in scope. - Forgetting
*inside the function.a = b;on two pointer parameters swaps the local copies, not the values. You want*a = *b;. - Using
sizeofon an array parameter. Always wrong. Pass the length. - Returning a pointer to a local variable. The local dies when the function returns, so the caller holds a dangling pointer, which is undefined behavior. Return by value, write into a caller-supplied buffer, or allocate with
malloc. - Not checking a pointer parameter for
NULL. A function that dereferences whatever it is handed will crash on a null argument.
Frequently Asked Questions
Does C have pass by reference?
No. C has exactly one mode: pass by value. A function always receives a copy of each argument, so assigning to a parameter never touches the caller's variable. What looks like pass by reference is passing a pointer by value - the pointer is copied, but it still points at the caller's object, so the function can modify it through the pointer.
How do you pass an array to a function in C?
Write the parameter as int a[] or int *a - they mean the same thing - and pass the length as a separate parameter: int sum(const int a[], int n). The array is not copied; the function receives a pointer to its first element, so changes to a[i] are visible to the caller.
Why does sizeof not work on an array parameter in C?
Because the parameter is really a pointer. sizeof(a) inside the function gives the size of a pointer (typically 8 bytes), not the size of the array - so sizeof(a)/sizeof(a[0]) yields something like 2 instead of the element count. Always pass the length explicitly.
How can a C function return more than one value?
Pass pointers for the extra results and write through them: int divide(int a, int b, int *quotient, int *remainder) returns a success flag and fills both outputs. The alternative is returning a struct that bundles the values together.