Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Pass Arrays to Functions
Learn passing an array to a function in C the way embedded engineers actually do it: one base address, no copying, and full control over the caller’s data. Part of our free C programming course for embedded systems.
In This Lecture
What You Will Learn
Welcome back to the free C tutorial on EmbeddedPathashala. In this lecture on passing an array to a function in C, we finish the story that started with pointers and arrays: what really travels across a function call when you write process(buffer). If you are following this free C programming course to prepare for embedded C programming work, this is one of the most practical ideas in the whole chapter, because every UART receive buffer, ADC sample window and DMA descriptor list you will ever handle goes through exactly this mechanism.
- Why an array name turns into the address of its first element when it appears in a function call.
- Why
int a[],int a[6]andint *aare the same parameter in disguise. - How the callee reaches the caller’s original elements, and why changes survive after the function returns.
- How to prove all of this with a small program you can build and run on your own Linux machine.
- The mistakes that cause silent buffer overruns on microcontrollers, and how to avoid them.
Prerequisites
You should be comfortable with one-dimensional arrays, the address-of operator, pointer declaration and dereferencing, and the equivalence of a[i] and *(a + i) from the earlier lectures of this chapter. A working GCC or Clang on Linux is enough for the demos; the same code builds with arm-none-eabi-gcc for a microcontroller target.
Passing an Array to a Function in C: The Core Idea
Imagine you are writing firmware that samples an ADC six times and then removes a fixed calibration offset from every sample. The samples live in an array inside main(). A separate function does the correction. How does that function get hold of six integers?
There are only two sensible designs. The function could receive a private copy of all six values, or it could be told where the original six values live. C chooses the second design for arrays, always, with no exceptions. When the compiler sees an array name used as a function argument, it replaces the name with the address of element zero. The function therefore receives a single pointer, and that pointer is initialised with the base address of the caller’s array.
This is not a convenience feature added for speed. It follows from the rule you already know: in most expressions an array name evaluates to a pointer to its first element. A function call argument is one of those expressions. So when you write apply_offset(adc, 6, 3), the first argument is exactly the same value as &adc[0].
What Crosses the Call Boundary
Because the callee holds the real address, it is working on the original data. There is no second array anywhere in memory. When the function writes samples[2] += 3, it is modifying main’s third element. That is why the book-style statement “changes made inside the function are reflected in the caller” is true for arrays, while it is false for plain int parameters, which are copied.
Why the Compiler Works This Way
Think about the alternative on a small microcontroller. A 512-byte UART buffer copied on every call would burn stack space you do not have and CPU cycles you cannot spare. Passing a 4-byte pointer (on a 32-bit Cortex-M) costs the same whether the buffer holds 6 bytes or 6 kilobytes. This is the performance story, and it is also the reason that C code interacting with hardware buffers and DMA can be written so compactly.
Three Ways to Declare the Array Parameter
C lets you spell the receiving parameter in three different ways. Beginners often think these differ in meaning. They do not.
| Spelling | What you might think | What the compiler actually stores |
|---|---|---|
int samples[] | An array of unknown size | A pointer: int *samples |
int samples[6] | An array of exactly six ints | A pointer: int *samples; the 6 is ignored |
int *samples | A pointer to one int | A pointer: int *samples |
In all three cases the function owns exactly one pointer variable and nothing else. The size written between the brackets is documentation for humans, not an instruction to the compiler. That is why you must always pass the element count as a separate argument, and we will see in the next lecture what goes wrong when you forget.
Three Spellings Collapse Into One Type
Subscript and Pointer Notation Inside the Function
Since the parameter is a pointer holding the base address, you can reach element i either as samples[i] or as *(samples + i). The compiler generates the same machine code for both. Use subscripts when you are thinking “the i-th sample” and pointer arithmetic when you are walking through a buffer, for example in a parser that advances a read pointer through received bytes.
Pass an Array to a Function in C: Build and Run
Let us prove every claim above with a program. Save the following as ep_adc_offset.c. It declares the same function three times using the three spellings, defines it once, and then checks that the callee really saw the caller’s address.
#include <stdio.h>
#include <stddef.h>
/* Three spellings of the same prototype - the compiler treats them as one. */
int *apply_offset(int samples[], size_t n, int offset);
int *apply_offset(int samples[6], size_t n, int offset);
int *apply_offset(int *samples, size_t n, int offset);
#define TYPE_NAME(x) _Generic((x), int *: "int *", default: "something else")
int *apply_offset(int *samples, size_t n, int offset)
{
printf("parameter type inside function: %s\n", TYPE_NAME(samples));
for (size_t i = 0; i < n; i++) {
samples[i] += offset;
}
return samples;
}
static void show(const char *tag, const int *v, size_t n)
{
printf("%s", tag);
for (size_t i = 0; i < n; i++) {
printf(" %d", v[i]);
}
printf("\n");
}
int main(void)
{
int adc[6] = {512, 498, 530, 505, 521, 517};
show("before:", adc, 6);
int *seen = apply_offset(adc, 6, 3);
show("after :", adc, 6);
printf("function saw the same address as main: %s\n",
seen == adc ? "yes" : "no");
return 0;
}
Build and run it with a modern C standard:
gcc -std=c11 -Wall -Wextra -o ep_adc_offset ep_adc_offset.c
./ep_adc_offset
Expected output:
before: 512 498 530 505 521 517
parameter type inside function: int *
after : 515 501 533 508 524 520
function saw the same address as main: yes
Reading the Output Line by Line
- The
_Genericmacro reports the parameter’s type asint *, even though one of the prototypes was written with brackets. This is the compiler confirming that the array syntax in a parameter list is just a pointer. - Every sample increased by 3, and the change is visible in
main()after the call. No copy was made. - The pointer returned by the function compares equal to
adc. The callee was given the original address.
-Wall, recent GCC versions print a -Warray-parameter warning when you redeclare the same function with different bracket spellings, as this demo does on purpose. The code is legal, but in real projects pick one spelling and use it everywhere. Most embedded teams standardise on const uint8_t *buf, size_t len.
Real-World Use Cases in Embedded C Programming
- Sensor filtering: a moving-average function receives the sample window and its length and returns a value, without copying the window.
- Protocol parsing: a CRC routine receives a pointer to a received frame and the number of valid bytes.
- Display drivers: a function that draws a glyph receives a pointer to a bitmap table stored in flash.
- DMA setup: the driver is handed the base address of a buffer that the DMA controller will fill in the background.
Read-Only Parameters and const
If a function only reads the array, say so. Writing const int *samples tells the compiler, and every future reader, that the function will not modify the caller’s data. The compiler then rejects accidental writes. In the demo above, show() uses const int *v for exactly this reason, while apply_offset() omits const because it is supposed to change the values.
Common Mistakes and Troubleshooting
| Mistake | Symptom | Fix |
|---|---|---|
| Forgetting to pass the length | Loop runs past the end, random corruption | Always pass size_t n next to the pointer |
Writing apply_offset(&adc, ...) | Compiler warning about incompatible pointer type | Pass adc or &adc[0]; &adc has a different type |
| Expecting the function to resize or reassign the caller’s array | Assigning samples = other has no effect outside | The pointer is a local copy; only the pointed-to elements are shared |
| Modifying a string literal or flash table through the pointer | Hard fault or crash | Declare read-only inputs as const |
Best Practices, Performance and Security
- Pass the pointer and the count together, in that order, every time.
- Mark input-only buffers
const; this documents intent and catches bugs at compile time. - Passing an array costs one pointer, so there is no performance reason to avoid helper functions for buffer work.
- Validate the length against the real buffer capacity before writing. An unchecked length is the root of most buffer-overflow vulnerabilities in firmware that parses external data.
- Use
size_tfor lengths and indexes so that very large buffers do not overflow a signed type.
Key Takeaways
- An array name in a call becomes the address of its first element.
a[],a[N]and*aare the same parameter type: a pointer.- The callee works on the caller’s original array, so its changes persist.
- The size is lost at the call boundary; carry it in a separate argument.
Conclusion
Passing an array to a function in C is really passing a pointer, and once you hold that picture in your head, a lot of confusing behaviour becomes obvious. Changes persist because there is only one array. The bracket size is ignored because the callee only has a pointer. The function cannot know how many elements exist because that information stayed behind in the caller. In the next lecture we look closely at that last point, because it produces one of the best-known traps in C: sizeof applied to an array parameter.
Suggested Images for This Lecture
- Diagram of caller array cells with a pointer in the callee pointing to element zero. Alt text: “passing an array to a function in C showing base address stored in a pointer parameter”.
- Screenshot of the build and run output. Alt text: “free C programming course demo of passing array to function in C with gcc output”.
- Table graphic of three equivalent array parameter declarations. Alt text: “embedded C programming array parameter spellings int a[] int a[6] int *a”.
Frequently Asked Questions
Does C pass arrays by value or by reference?
Strictly speaking C always passes by value, but the value being passed for an array is a pointer to its first element. The effect looks like pass by reference: the function can modify the caller’s elements.
Is there any difference between int a[] and int *a in a parameter list?
No. In a function parameter, both mean pointer to int. The difference only matters in other contexts, such as declaring a local array versus a local pointer.
Why does my function not know the array length?
Because only the base address crosses the call. The element count is a property of the original array’s type, which the callee never sees. Pass the length as an extra argument.
Can I stop a function from changing my array?
Yes. Declare the parameter as const int *a. Any attempt to write through it becomes a compile error.
Does passing a large array slow down my embedded program?
No. Only one pointer is passed, so the call costs the same for 6 elements or 6000. The loop inside the function is what takes time.
Can the function make the caller’s array name point somewhere else?
No. An array name is not a modifiable variable, and the function only has its own pointer copy. Reassigning that copy does not affect the caller.
Is this course really free?
Yes. EmbeddedPathashala offers a free C programming course, a free embedded C course, and free Linux kernel and device driver courses for students.
Keep Going With the Free Embedded C Course
Next we explore the sizeof trap that catches almost every beginner. Continue the free C tutorial or browse the full course index.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments