What are Pass Arrays to Functions- C Programming Course For Engineering Students

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Free C Programming Course · Chapter 8 · Pointers

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.

3
Prototype spellings, one meaning
0
Bytes of array data copied
1
Pointer received by the callee

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] and int *a are 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

main()adc[0] = 512adc[1] = 498adc[2] = 530adc[3] = 505adc[4] = 521adc[5] = 517
callapply_offset(adc, 6, 3) sends only the base address of adc[0]
calleesamples = address of adc[0]→ points back into main’s six cells

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.

SpellingWhat you might thinkWhat the compiler actually stores
int samples[]An array of unknown sizeA pointer: int *samples
int samples[6]An array of exactly six intsA pointer: int *samples; the 6 is ignored
int *samplesA pointer to one intA 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

int samples[]→int *samples
int samples[6]→int *samples
int *samples→int *samples

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 _Generic macro reports the parameter’s type as int *, 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.
Compiler note: with -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

MistakeSymptomFix
Forgetting to pass the lengthLoop runs past the end, random corruptionAlways pass size_t n next to the pointer
Writing apply_offset(&adc, ...)Compiler warning about incompatible pointer typePass adc or &adc[0]; &adc has a different type
Expecting the function to resize or reassign the caller’s arrayAssigning samples = other has no effect outsideThe pointer is a local copy; only the pointed-to elements are shared
Modifying a string literal or flash table through the pointerHard fault or crashDeclare 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_t for 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 *a are 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

Leave a Reply

Your email address will not be published. Required fields are marked *