What is Passing Arrays to C Functions- C Programming Course With Examples

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

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.

3
Tested examples
10
Quiz questions
10
Interview questions

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 sizeof gives 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.

Array Decay: What the Function Really Receives
3
readings[0]
6
readings[1]
2
readings[2]
7
readings[3]
1
readings[4]
function parameter arr→address of readings[0]

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

QuestionAnswer
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 callOne pointer, regardless of array size

Common Mistakes and Troubleshooting

MistakeResultFix
Using sizeof on the parameterWrong element countPass the length as an argument
Passing a length larger than the real arrayOut-of-bounds access, corrupted memoryCompute the length where the array is declared
Forgetting const on read-only dataAccidental writes go unnoticedUse const int *
Expecting a copy to be madeCaller’s data changes unexpectedlyCopy explicitly with memcpy if you need a private version
Passing a 2-D array as int **Compiler warning or crashUse int m[][COLS] with the column count

Best Practices

  • Always pass (pointer, length) together. Make it a reflex.
  • Use const for input arrays and plain pointers only for arrays the function must fill.
  • Use size_t for lengths and loop counters over arrays.
  • Validate NULL and 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.
  • sizeof inside the function measures a pointer, so pass the length separately.
  • int a[], int a[10] and int *a are the same parameter type.
  • Use &arr[k] to pass a slice and const to 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.

  1. In C, a function parameter that is an ordinary int receives:

    • 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.

  2. 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.

  3. 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.

  4. 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).

  5. 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.

  6. 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 *.

  7. On a 64-bit PC, what does sizeof(arr) print inside void 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.

  8. How do you pass the last four elements of uint8_t buf[8] to byte_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] (or buf + 4) is the address of element 4. Option D would sum the first four elements instead.

  9. 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.

  10. 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 parameter v is a local pointer, so v++ moves it to element 1 without touching the caller’s array name. Then *v = 50 writes into e[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.

  1. 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.

  2. Why does a swap function with int parameters not work?

    Show answer

    The function swaps its own copies. The caller’s variables are untouched, so you must pass pointers.

  3. 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.

  4. Why does sizeof on 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.

  5. What is the difference between int a[] and int *a as 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.

  6. 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.

  7. 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.

  8. Why is const on 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.

  9. 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.

  10. How would you write a safe function that fills an array for the caller?

    Show answer

    Take a pointer and a capacity, check for NULL and 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

Leave a Reply

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