void process(int arr[], int n);
// bracket formvoid process(int arr[10], int n);
// bracket with a sizevoid process(int *arr, int n);
// pointer formPassing Arrays to C Functions: Methods and Example Programs
What is Passing Arrays to C Functions? Core Concept & Direct Summary
How are arrays passed to functions via pointer decay, and how can array modifications in functions persist?
By this point in the Arrays cluster, three separate tutorials have bumped into the same fact and promised to explain it later: when you pass an array to a function, you aren't really passing the array. You're passing a pointer to its first element. That single design decision explains why sizeof fails inside functions, why array parameters need a separate size argument, and why a function can modify your array even though C is pass-by-value.
This page is the payoff. We'll cover the parameter syntaxes, what actually gets passed, how to modify arrays through functions, const protection, the 2D array case, the "how do I return an array?" question, and a couple of complete programs that pull it all together.
(Suggested image: the hero diagram — main() holding a 5-element array with a single arrow labeled "address of element 0" pointing to a function box, and a return arrow labeled "nothing to give back")
How Does Passing Arrays to C Functions Work? In-Depth Mechanics & Architecture
The Syntax: Three Ways to Write It
All three of these declarations mean exactly the same thing to the compiler:
int arr[]— the friendliest, most common way to signal "this is an array of ints"int arr[10]— the10is completely ignored by the compiler. It's pure documentation, and arguably misleading documentation, since the function will happily accept arrays of any lengthint *arr— the honest, mechanical truth: it's a pointer
Pick the bracket form for readability in most code; reach for the pointer form when the function is genuinely pointer-oriented (doing arithmetic rather than indexing). Just know that they're interchangeable, and the compiler doesn't care which you choose.
And in every case, remember the size parameter. As we worked through in the array-size tutorial, the function has no way to know how long the array is — sizeof won't help — so the count travels separately:
(Suggested image: the three parameter forms shown as three code cards with an equals sign between them and a note "compiler sees these as identical")
What Actually Gets Passed: The Pointer
Recall the memory model from the arrays pillar page: array elements sit contiguously, and the array name acts as the address of element 0. So when you call printArray(marks, 5), what travels into the function is a single memory address — not 20 bytes of copied data.
Typical output:
All three addresses are identical. The array name is the address of its first element, and that's precisely what the function receives. No copy of the data was made.
Two big consequences:
- Efficiency. Passing a 10,000-element array to a function costs one pointer's worth of copying — not 40,000 bytes. Arrays are effectively passed "by reference," cheaply, whether you intended that or not.
- The function can modify your data. Since it holds the address, writing through it changes the original array — despite C being pass-by-value. This is the whole reason arrays behave differently from plain variables.
Modifying Arrays Inside Functions
A regular variable passed to a function gets copied, so the original is safe:
But an array element accessed through a passed pointer writes to the original memory:
Output:
The function never returned anything — yet main's array came back doubled. That's the pointer in action.
This is enormously useful: in-place sorting, filling arrays from input, normalizing data, swapping elements. Any function whose job is "change this collection" uses this mechanism.
(Suggested image: the before/after diagram — main's array boxes, an arrow into the function, the function writing new values into those same boxes, then main seeing the new values. Emphasize "same memory, not a copy.")
Protecting Arrays With const
Because functions can modify arrays, a careless function can wreck your data — or worse, you assume it won't and get surprised. C's answer is const on the parameter:
Now the compiler enforces the promise:
Why this deserves to be a habit, not an afterthought:
- It documents intent. Reading the prototype tells you instantly whether the function reads or writes your data.
- It prevents bugs. Accidental writes become compile errors instead of silent corruption.
- It enables optimization. The compiler knows the data won't change and can optimize accordingly.
- It's self-enforcing documentation — unlike a comment saying "don't modify this," which nothing checks.
The professional rule: if a function doesn't need to modify your array, mark it const — and if a function does need to modify it, make that obvious in the name (doubleThem, sortArray, fillWith) so callers know what they're signing up for.
(Suggested image: a "read-only" lock icon on a const array parameter vs an "editable" pencil icon on a non-const one)
The "How Do I Return an Array?" Question
Short answer: you can't — not directly.
That second version is one of the most infamous beginner traps in C. It compiles (maybe with a warning). It might even appear to work in a small test — and then fails mysteriously later. Here's why: arr is a local variable that lives on the stack; when the function returns, that memory is reclaimed. The pointer you returned now points at a stack frame that no longer belongs to you. Anything can overwrite it.
The three correct approaches:
Approach 1: The caller supplies the array (output parameter)
The standard C idiom. The caller owns the memory; the function fills it:
Output:
This is how most C library functions work — fgets fills your buffer, qsort rearranges your array in place. The caller owns storage; the function does the work.
Approach 2: static local array
Works, with a serious caveat:
The static keyword (from our storage classes tutorial) gives the array program-long lifetime, so returning its address is legitimate. The catch: there's only one copy, shared across all calls. Call getPrimes() twice and both pointers refer to the same memory. It also isn't thread-safe. Fine for a constant lookup table; wrong for anything that needs fresh data per call.
Approach 3: Dynamic allocation with malloc
The fully flexible answer, and the only one that lets each call return an independent array:
Output:
This is the real answer for "return an array from a function" — but it comes with the ownership rule: whoever allocates, frees; or whoever receives, frees. Forget the free() and you have a memory leak. (Dynamic allocation gets its own full tutorial in the Pointers cluster; this is the preview.)
(Suggested image: a memory diagram comparing the three approaches — stack array dying on return, static array persisting, heap array persisting until free() — with a "danger" marker on the returning-stack-pointer case)
Passing 2D Arrays to Functions
Two-dimensional arrays have a stricter rule (which you saw in the 2D and multidimensional tutorials): the column count is mandatory in the parameter:
Why? To compute mat[r][c], the compiler must know how many integers are in each row — element [r][c] lives at offset (r × COLS + c). Without COLS, it can't do the arithmetic. It's the same underlying reason int bad[3][] can't be declared.
The #define/constant habit keeps this clean and self-updating:
One more option for true flexibility: passing a pointer to the first element and doing the index math yourself, which lets the dimensions be fully runtime values:
That r * cols + c formula is the row-major order formula from the 2D page — and this flattening approach is how a lot of real matrix and image code handles variable dimensions.
A Complete Example: A Small Statistics Library
Everything from this page, doing real work:
Sample Run:
Read the function signatures as a map of intentions:
const int arr[], int n→ "I only read your data"int arr[], int n→ "I will change your data"int dest[], const int src[], int n→ "I'll copy src into dest"
And notice sizeof is used only in main, where data is a genuine array — never inside a function. That's the discipline from the array-size tutorial, applied.
(One small note on the demo: the "backup" copy happens after the modifications, so it copies the modified data. If you wanted a true pre-modification backup, you'd call copyArray earlier. I've kept the order as written because it demonstrates the pattern clearly, but a careful instructor would move it up — worth adjusting if you publish the code.)
Common Array-Passing Mistakes
- Using
sizeofinside the function to find the length —arris a pointer there; you get 8 (or 4), not the element count. Pass the size in. This is the mistake. - Forgetting the size parameter entirely — the function compiles, then loops past the end of the array because it has no idea where "end" is.
- Returning a pointer to a local (non-static) array — the classic dangling-memory bug. Use output parameters,
static, ormalloc. - Modifying an array you didn't mean to modify — since passing is by address, any function can change your data. Use
conston read-only parameters and check the signatures you call. - Omitting the column size for a 2D array parameter —
void f(int m[][])doesn't compile. Column count is always required. - Confusing
arr[i]and*arrsemantics in pointer-form parameters —arr[i]is element i;*arris just element 0. Related, but not the same thing. - Forgetting to
freememory allocated in a creating function — if a function returns amalloc'd array, the caller owns it and must free it. - Assuming a function that takes
const int arr[]accepts a modified pointer elsewhere —constis a promise the compiler enforces at that boundary; it doesn't make the underlying data immutable for everyone.
(Suggested image: "8 Array-Passing Mistakes" checklist graphic — consistent with the series)
Final Thoughts
Passing arrays to functions comes down to one realization: arrays travel as addresses, not as copies. Everything else — needing a size parameter, functions being able to modify your data, sizeof failing inside the function, const mattering — is a direct consequence of that single fact.
The habits worth adopting permanently: every array parameter comes with an int n, every read-only array parameter gets const, the caller owns the memory (so return arrays via output parameters or malloc, never by returning a local's address), and 2D array parameters always name their column count.
Get those four right and your array functions will be correct and readable by default — which puts you well ahead of most people writing C.
Next in this cluster: working with arrays of strings, where arrays, characters, and pointer-to-pointer syntax all meet.
Runnable C Code Example & Practical Implementation
#include <stdio.h>
// Modifies elements in place
void doubleElements(int *arr, size_t n) {
for (size_t i = 0; i < n; i++) {
arr[i] *= 2;
}
}
int main() {
int nums[] = {
5, 10, 15, 20
};
size_t len = sizeof(nums) / sizeof(nums[0]);
doubleElements(nums, len);
printf("Doubled array:\
");
for (size_t i = 0; i < len; i++) {
printf("%d ", nums[i]);
}
printf("\
");
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
const (const int *arr) to enforce read-only immutability.Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: Can you pass an array by value (full copy) in C?
Q: Why should read-only array parameters be marked `const`?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.