Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Passing Arrays to C Functions
Passing an array to a function in C does not copy the array. The function receives the address of the first element, which is why it can change your data and why it cannot tell how long the array is. Includes a full quiz and interview questions for embedded C programming.
What You Will Learn
- What actually happens when you start passing an array to a function in C
- Why changes inside the function affect the caller’s array
- Why
sizeofgives the wrong answer inside the function, and how to pass the length properly - Three equivalent ways to write an array parameter
- How to pass only part of an array with pointer arithmetic
- How to protect read-only data with
const - Quiz and interview questions covering the entire call by value, call by reference and pointer-return set
Prerequisites
You need the earlier lectures on pointers and 1-D arrays (array name versus pointer, pointer arithmetic) and the previous four lectures on call by value, call by reference, returning several values and returning pointers.
Arrays Decay to Pointers in Calls
In the lecture on call by value we saw that C copies each argument. Arrays seem to break that rule, because a function can change the array’s contents. They do not break it. What C copies is the address of the first element. When an array name appears as an argument, it is automatically converted to a pointer to element 0. This conversion is called array decay.
That single fact explains everything in this lecture. The function receives a pointer, so it can modify the original elements, it knows nothing about the array’s length, and passing a million-element array costs the same as passing one element: one machine word.
Three Equivalent Array Parameters
Inside a parameter list the following three declarations mean exactly the same thing to the compiler:
void process(int arr[], size_t n);
void process(int arr[10], size_t n); /* the 10 is ignored */
void process(int *arr, size_t n);
The number in brackets is only a comment to human readers. Many teams prefer the pointer form int *arr because it is honest about what is passed, while the bracket form int arr[] signals “this points to the start of several elements”.
Example 1: Why sizeof Fails Inside the Function
Because the parameter is a pointer, sizeof measures the pointer, not the array. The program below prints both sizes.
#include <stdio.h>
#include <stddef.h>
static void inspect(int arr[], size_t n)
{
printf("inside inspect : sizeof(arr) = %zu, n = %zu\n", sizeof(arr), n);
}
int main(void)
{
int readings[5] = {3, 6, 2, 7, 1};
printf("inside main : sizeof(readings) = %zu\n", sizeof(readings));
inspect(readings, sizeof readings / sizeof readings[0]);
return 0;
}
Build and run. GCC even warns about the mistake:
gcc -std=c11 -Wall -Wextra -o decay decay.c
decay.c: In function 'inspect':
decay.c:6:67: warning: 'sizeof' on array function parameter 'arr' will return size of 'int *' [-Wsizeof-array-argument]
./decay
inside main : sizeof(readings) = 20
inside inspect : sizeof(arr) = 8, n = 5
In main(), 5 ints of 4 bytes give 20 bytes. Inside inspect() the answer 8 is the size of a pointer on a 64-bit PC. On a 32-bit microcontroller such as a Cortex-M the same line prints 4. Either way it is not 20. The fix is simple and universal: compute the element count where the array is defined and pass it along as an extra argument, as n does here.
Warning: Never write sizeof(arr) / sizeof(arr[0]) inside a function that received arr as a parameter. It silently returns pointer size divided by element size, usually 2 or 1, and your loop will run the wrong number of times.
Example 2: Modifying the Caller’s Array
Since the function works on the caller’s elements, writes are visible after the call. Here one function doubles every element and another prints them. Note that the printing function takes a const array, promising not to change anything.
#include <stdio.h>
#include <stddef.h>
static void print_all(const char *label, const int values[], size_t n)
{
printf("%s", label);
for (size_t i = 0; i < n; i++) {
printf("%d ", values[i]);
}
printf("\n");
}
static void double_all(int values[], size_t n)
{
for (size_t i = 0; i < n; i++) {
values[i] *= 2;
}
}
int main(void)
{
int data[5] = {3, 6, 2, 7, 1};
size_t n = sizeof data / sizeof data[0];
print_all("before : ", data, n);
double_all(data, n);
print_all("after : ", data, n);
return 0;
}
before : 3 6 2 7 1
after : 6 12 4 14 2
No pointer syntax appears in the loop because subscripting works on pointers: values[i] is shorthand for *(values + i). This is the same equivalence you met in the pointers-and-arrays lectures.
Example 3: Buffers and Sub-Arrays in Embedded C
Firmware constantly passes byte buffers around: UART frames, DMA blocks, flash pages. Because the callee receives only an address and a length, you can also hand it part of a buffer without copying anything. This program fills a frame with a rising pattern, then computes a checksum over the whole frame and over its second half.
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
static void fill_pattern(uint8_t *buf, size_t len, uint8_t start)
{
for (size_t i = 0; i < len; i++) {
buf[i] = (uint8_t)(start + i);
}
}
static uint32_t byte_sum(const uint8_t *buf, size_t len)
{
uint32_t sum = 0;
for (size_t i = 0; i < len; i++) {
sum += buf[i];
}
return sum;
}
int main(void)
{
uint8_t frame[8];
fill_pattern(frame, sizeof frame, 0x10);
printf("whole frame : sum = 0x%02X\n", (unsigned int)byte_sum(frame, sizeof frame));
printf("second half : sum = 0x%02X\n", (unsigned int)byte_sum(&frame[4], 4));
return 0;
}
whole frame : sum = 0x9C
second half : sum = 0x56
Check the numbers by hand: the frame holds 0x10 to 0x17, which is 16 to 23 and adds up to 156, or 0x9C. The second half holds 0x14 to 0x17, which is 20 + 21 + 22 + 23 = 86, or 0x56. The expression &frame[4] (or equivalently frame + 4) is simply the address of element 4, and the callee sees it as the start of a four-element array.
Array Parameter Rules at a Glance
| Question | Answer |
|---|---|
| What is passed? | Address of the first element (a pointer) |
| Are elements copied? | No |
| Can the function change elements? | Yes, unless the parameter is const |
| Does the function know the length? | No, pass it separately |
What does sizeof(param) return? | Size of a pointer |
| How to pass a slice? | &arr[k] or arr + k with a new length |
| Cost of the call | One pointer, regardless of array size |
Common Mistakes and Troubleshooting
| Mistake | Result | Fix |
|---|---|---|
Using sizeof on the parameter | Wrong element count | Pass the length as an argument |
| Passing a length larger than the real array | Out-of-bounds access, corrupted memory | Compute the length where the array is declared |
Forgetting const on read-only data | Accidental writes go unnoticed | Use const int * |
| Expecting a copy to be made | Caller’s data changes unexpectedly | Copy explicitly with memcpy if you need a private version |
Passing a 2-D array as int ** | Compiler warning or crash | Use int m[][COLS] with the column count |
Best Practices
- Always pass
(pointer, length)together. Make it a reflex. - Use
constfor input arrays and plain pointers only for arrays the function must fill. - Use
size_tfor lengths and loop counters over arrays. - Validate
NULLand zero length at the top of public functions. - Performance: passing an array is as cheap as passing one pointer, so there is no need to avoid the call on small MCUs. Beware of wrapping an array inside a struct, because passing the struct by value copies all of it.
- Security: the missing length is the cause of countless buffer overflows. Never let a function write past the length it was given, and never trust a length that came from the network without checking it against the buffer size.
Key Takeaways
- An array argument decays into a pointer to its first element.
- The function works on the caller’s elements, so changes persist.
sizeofinside the function measures a pointer, so pass the length separately.int a[],int a[10]andint *aare the same parameter type.- Use
&arr[k]to pass a slice andconstto protect inputs.
Conclusion
Passing arrays to functions in C is cheap, flexible and dangerous in equal measure. It is cheap because only an address moves, flexible because any slice of any buffer can be handed over, and dangerous because the length is invisible to the callee. If you always pass the length, use const for inputs and compile with warnings on, you will write array-handling code that scales from a classroom exercise to a production firmware driver. This completes the set on call by value, call by reference, multiple return values, returning pointers and array parameters in our free C programming course. Test yourself with the quiz below.
Quiz: Test Your Understanding
Try each question first, then open the answer. The questions cover all five lectures in this set.
-
In C, a function parameter that is an ordinary
intreceives:- A. The caller’s variable itself
- B. A copy of the caller’s value
- C. The address of the caller’s variable
- D. A reference
Show answer
B. C passes arguments by value, so the function works on a copy.
-
Which change makes
swap(int x, int y)actually exchange two caller variables?- A. Add the keyword
static - B. Use pointer parameters and call
swap(&a, &b) - C. Return void
- D. Rename the parameters
Show answer
B. The function needs the addresses of the variables to modify them.
- A. Add the keyword
-
What does
*p++do?- A. Increments the value p points to
- B. Reads the value at p, then advances the pointer p
- C. Decrements p
- D. Causes a compile error
Show answer
B. Postfix
++binds tighter than*. Use(*p)++to increment the value. -
How can a function give back a quotient, a remainder and an error status?
- A. Three return statements
- B. Return the status and write the other two through pointer parameters
- C. Use global variables only
- D. It cannot
Show answer
B. Return a status code and deliver results through output parameters (or return a struct).
-
Which function is a bug?
- A.
int *f(int *a) { return a + 1; } - B.
int *f(void) { static int x = 3; return &x; } - C.
int *f(void) { int x = 3; return &x; } - D.
const char *f(void) { return "ok"; }
Show answer
C. It returns the address of a local variable that no longer exists after the function returns.
- A.
-
Which prototype is not equivalent to the others?
- A.
void f(int a[]); - B.
void f(int *a); - C.
void f(int a[10]); - D.
void f(int a[][10]);
Show answer
D. It declares a pointer to an array of 10 ints, which is a different type. The other three are all
int *. - A.
-
On a 64-bit PC, what does
sizeof(arr)print insidevoid f(int arr[])?- A. The size of the whole array
- B. The number of elements
- C. 8, the size of a pointer
- D. 0
Show answer
C. The parameter is a pointer. On a 32-bit microcontroller the answer would be 4.
-
How do you pass the last four elements of
uint8_t buf[8]tobyte_sum?- A.
byte_sum(buf[4], 4) - B.
byte_sum(&buf[4], 4) - C.
byte_sum(*buf + 4, 4) - D.
byte_sum(buf, 4)
Show answer
B.
&buf[4](orbuf + 4) is the address of element 4. Option D would sum the first four elements instead. - A.
-
What is the output of this program?
static void reset(int v[], int n) { for (int i = 0; i < n; i++) { v[i] = 0; } } int main(void) { int d[3] = {4, 5, 6}; reset(d, 2); printf("%d %d %d\n", d[0], d[1], d[2]); return 0; }Show answer
0 0 6. The function clears the first two elements of the caller’s array. -
What is the output of this program?
static void shift(int v[]) { v++; *v = 50; } int main(void) { int e[3] = {1, 2, 3}; shift(e); printf("%d %d %d\n", e[0], e[1], e[2]); return 0; }Show answer
1 50 3. The parametervis a local pointer, sov++moves it to element 1 without touching the caller’s array name. Then*v = 50writes intoe[1].
Interview Questions on Functions and Pointers
These questions are common in embedded C interviews at product and service companies. Try to answer aloud before reading the answer.
-
Does C support call by reference?
Show answer
No. C passes everything by value. Call by reference is simulated by passing the address of a variable and dereferencing it in the function.
-
Why does a swap function with
intparameters not work?Show answer
The function swaps its own copies. The caller’s variables are untouched, so you must pass pointers.
-
Can you pass an array by value in C?
Show answer
Not directly, because the array name decays to a pointer. If you really need a copy, wrap the array in a struct and pass the struct by value, or copy it yourself with
memcpy. Be careful about the stack cost on small devices. -
Why does
sizeofon an array parameter return the pointer size?Show answer
Because an array parameter is adjusted to a pointer type by the compiler. The original length information is not part of that type.
-
What is the difference between
int a[]andint *aas function parameters?Show answer
None. They are the same type. Outside a parameter list, however, an array and a pointer are very different: an array is storage, a pointer is a variable holding an address.
-
What is wrong with returning the address of a local variable?
Show answer
The variable lives in the function’s stack frame and is gone after return. The pointer dangles, and using it is undefined behaviour. Return a pointer to static data, to the caller’s data, or fill a caller-supplied buffer.
-
How do you return more than one value from a function?
Show answer
Use output parameters (pointers), usually with a status code as the return value, or return a small struct. For variable-length data, fill a buffer provided by the caller.
-
Why is
conston pointer parameters valuable in embedded code?Show answer
It documents that the function will not modify the data, lets the compiler reject accidental writes, and allows read-only tables to live in flash instead of RAM.
-
Why is a static buffer returned from a function risky in an RTOS or with interrupts?
Show answer
All callers share the same storage, so a second task or an ISR can overwrite the result before the first caller has used it. The function is not reentrant. Use a caller-supplied buffer instead.
-
How would you write a safe function that fills an array for the caller?
Show answer
Take a pointer and a capacity, check for
NULLand zero capacity, never write past the capacity, and return the number of elements written or an error code so the caller can detect truncation.
Frequently Asked Questions
Is an array passed by value or by reference in C?
Neither exactly. The array name decays to a pointer to its first element, and that pointer is passed by value. The effect looks like call by reference because the elements are not copied.
How does a function know the size of an array it receives?
It does not. You pass the length as a separate parameter, or use a sentinel value such as the terminating zero in a C string.
Can I stop a function from changing my array?
Yes. Declare the parameter const int *arr or const int arr[]. Any attempt to write through it becomes a compile error.
How do I pass part of an array?
Pass the address of the first element you want, such as &arr[k], along with the number of elements to process.
Does passing a large array slow down the call?
No. Only one pointer is passed, so the cost is the same for 10 elements or 10,000.
How are two-dimensional arrays passed?
Declare the parameter with every dimension except the first, for example int m[][4], so the compiler knows the row size when computing element addresses.
Continue Your Free Embedded C Journey
You have finished call by value, call by reference, multiple return values, returning pointers and array parameters. Keep building skills with the free C programming course and free embedded C course from EmbeddedPathashala.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments