What is Call by Value in C- Embedded C Programming Course

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 · Pointers · Lecture 29

Call by Value in C

Find out what really happens to your variables when you call a function, why a swap function can fail, and how pointers open the door to call by reference. Includes a quiz and interview questions for the whole set.

15 min
Reading time
Beginner
Level
10 + 10
Quiz and interview questions

Call by value in C is the rule that every argument you pass to a function is copied into the function’s own parameter. The function works on the copy, so the original variable in the caller stays untouched. This lecture of our free C programming course explains the rule with a tested program, shows why a naive swap function does nothing, and connects the idea to pointers. It is core knowledge for embedded C programming, where drivers, interrupt handlers and HAL functions pass data in and out all day. At the end you will find a quiz and interview questions covering this whole set of lectures.

What You Will Learn

  • The difference between actual arguments and formal parameters
  • Why a function cannot change the caller’s variable by default
  • Why C has only call by value, and how pointers simulate call by reference
  • Why a swap function written with plain ints fails
  • How arrays and structures behave when passed to functions
  • How to answer common interview questions on this topic

Prerequisites

To understand call by value in C you should know how to write a function, declare variables and use the address-of and dereference operators. The earlier lectures of this chapter on pointer basics and arrays help, but nothing beyond a standard C compiler is needed for this free C tutorial.

What Call by Value in C Means

When you call bump(hits), the value stored in hits is evaluated and copied into a brand-new variable that belongs to bump. The value you write in the call is the actual argument. The variable declared in the function header is the formal parameter. They hold the same number at the start, but they are two separate objects at two different addresses.

What happens when bump(hits) is called
main() framehits5
copy made at the callvalue 5 travels
bump() framecount5, then 6 after count + 1
after the call returnshitsstill 5

Because the callee only has a copy, any change it makes disappears when the function returns. That is a feature, not a flaw: a function can use its parameters as scratch variables without worrying about damaging the caller’s data.

Call by Value in C vs Call by Reference

FeatureCall by valueCall by reference
What the function receivesA copy of the valueA way to reach the original variable
Can the function change the caller’s variable?NoYes
Does C support it directly?Yes, this is the only mechanismNo, but you can simulate it with pointers
How you simulate it in CNot neededPass &variable and use *parameter inside
Cost for a large structWhole struct is copiedOnly an address is copied

An important detail

When you pass a pointer, the pointer itself is still passed by value: the address is copied into the parameter. What makes changes visible is that both copies of the address lead to the same original object. So C is always call by value, and “call by reference” in C really means passing an address by value.

Demo: Call by Value in C in Action

This program shows three things: a copy that changes while the original stays the same, a swap that does nothing, and a short preview of the pointer-based fix. Save it as ep_call_by_value.c.

#include <stdio.h>

/* Gets a COPY of the caller's value. */
static void bump(int count, const int *caller_addr)
{
    count = count + 1;
    printf("inside bump : count = %d\n", count);
    printf("inside bump : same object as caller's variable? %s\n",
           (&count == caller_addr) ? "yes" : "no");
}

/* Classic mistake: swaps two private copies. */
static void swap_bad(int a, int b)
{
    int t = a;
    a = b;
    b = t;
    printf("inside swap_bad : a = %d, b = %d\n", a, b);
}

/* Peek ahead: handing over addresses lets the callee reach the originals. */
static void swap_ok(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

int main(void)
{
    int hits = 5;
    int x = 3, y = 9;

    bump(hits, &hits);
    printf("back in main : hits = %d\n\n", hits);

    swap_bad(x, y);
    printf("back in main : x = %d, y = %d\n\n", x, y);

    swap_ok(&x, &y);
    printf("after swap_ok : x = %d, y = %d\n", x, y);
    return 0;
}
gcc -std=c11 -Wall -Wextra -pedantic -o ep_call_by_value ep_call_by_value.c
./ep_call_by_value

Expected output:

inside bump : count = 6
inside bump : same object as caller's variable? no
back in main : hits = 5

inside swap_bad : a = 9, b = 3
back in main : x = 3, y = 9

after swap_ok : x = 9, y = 3

Reading the output line by line

  • count = 6, hits = 5: the function incremented its own copy only.
  • “same object … no”: comparing &count with the caller’s &hits proves they are two different objects.
  • swap_bad shows a = 9, b = 3: inside the function the swap really happened, but on copies. Back in main, x and y are unchanged.
  • swap_ok works: it receives the addresses of x and y, so *a and *b are the original variables themselves.

Next step

We study the pointer technique behind swap_ok, true call by reference simulation, in the next lecture. For now, just remember the key idea: to change the caller’s variable, send its address.

Arrays and Structures as Arguments

Arrays behave differently from plain ints, and the earlier lectures explain why. When you pass an array name, the array decays to a pointer to its first element, and that pointer is copied. The elements themselves are not copied. As a result, a function can modify the caller’s array through the pointer. This is exactly what happened when we passed a row such as readings[i] in the 2D array lectures: the function saw the original row, not a copy.

Structures are different again. A struct passed by value is copied in full, so a large struct costs stack space and time. In embedded C programming the usual habit is to pass a const pointer to the struct for read-only access, and a plain pointer when the function must update it.

You passWhat is copiedCan the callee change the original?
int, char, floatThe valueNo
PointerThe addressYes, through the pointer
Array nameThe address of the first elementYes, the elements
struct by valueThe whole structNo
const struct pointerThe addressNo, the compiler rejects writes

Where This Shows Up in Embedded Systems

  • A function such as uart_set_baud(int baud) can compute with its copy freely; the caller’s variable is safe. That is call by value in C protecting your data.
  • A driver that must return several results, for example a value and a status, takes pointers as output parameters.
  • Interrupt handlers and callbacks often receive a pointer to a context structure, because copying the whole structure inside an ISR would be slow.
  • Large configuration tables are passed as const pointers so that they stay in flash and are never duplicated on a small stack.

Common Mistakes With Call by Value in C

MistakeWhat goes wrongFix
Writing swap(int a, int b)Only the copies are swappedUse swap(int *a, int *b) and call swap(&x, &y)
Expecting a modified parameter to update the callerThe change is lost on returnReturn the new value or pass an address
Passing a large struct by value in an ISRSlow and stack hungryPass a const pointer
Returning the address of a parameterThe parameter dies when the function returnsNever return the address of a local or parameter
Warning: a pointer to a parameter or local variable becomes invalid as soon as the function returns. Using it later is undefined behaviour, and the bug often appears only on the target hardware.

Best Practices for Call by Value in C

  • Pass small values (ints, enums, flags) by value. It is simple and safe.
  • Pass an address when the function must modify the caller’s data, and document which parameters are outputs.
  • Pass const pointers for large read-only data.
  • Performance: copying a 4-byte int is free; copying a 200-byte struct on every call is not.
  • Safety: check pointer parameters for NULL before dereferencing them if callers are not under your control.

Summary and Key Takeaways

  • Call by value in C means every argument is passed as a copy into the parameter.
  • Changes to a parameter do not affect the caller’s variable.
  • To modify the caller’s data, pass its address and dereference inside the function.
  • Arrays decay to a pointer, so their elements can be changed by the callee.
  • Large structs are better passed by const pointer than by value.

Quiz: Pointers and Arrays Checkpoint

Ten questions on the last five lectures. Think first, then open the answer. Assume a 4-byte int.

  1. int a[3][4] starts at address 0x1000. What is the value of a + 1?

    • A. 0x1004
    • B. 0x1010
    • C. 0x1001
    • D. 0x100C
    Show answer

    B. a points to rows of 16 bytes, so a + 1 is 0x1000 + 16 = 0x1010.

  2. In an expression, what is the type of *(a + 2) for int a[3][4]?

    • A. int
    • B. int *
    • C. int (*)[4]
    • D. int **
    Show answer

    B. *(a + 2) is row 2, an array of 4 ints, which decays to int *.

  3. Which declaration makes p a pointer to an array of 5 ints?

    • A. int *p[5];
    • B. int (*p)[5];
    • C. int **p[5];
    • D. int p[5]*;
    Show answer

    B. The parentheses bind the star to p first. A declares an array of 5 pointers.

  4. What does int *q[5]; declare?

    • A. A pointer to 5 ints
    • B. An array of 5 pointers to int
    • C. A pointer to a pointer
    • D. A syntax error
    Show answer

    B. The subscript binds tighter than the star, so q is an array of 5 int pointers.

  5. With int m[3][4] = {{10,11,12,13},{20,21,22,23},{30,31,32,33}}, what is *(*(m + 2) + 1)?

    • A. 21
    • B. 32
    • C. 31
    • D. 23
    Show answer

    C. Row 2 is {30,31,32,33} and column 1 is 31.

  6. For int cube[2][3][4], what is sizeof cube[1]?

    • A. 96
    • B. 48
    • C. 16
    • D. 12
    Show answer

    B. One element of cube is a 3 by 4 block: 3 x 4 x 4 bytes = 48.

  7. What is the byte offset of cube[1][2][3] from the start of cube?

    • A. 92
    • B. 88
    • C. 23
    • D. 96
    Show answer

    A. Index = (1 x 3 + 2) x 4 + 3 = 23, and 23 x 4 = 92.

  8. What does this print? void f(int n) { n = n * 2; } … int v = 7; f(v); printf(“%d”, v);

    • A. 14
    • B. 0
    • C. 7
    • D. Undefined
    Show answer

    C. f changes its own copy, so v is still 7.

  9. Which parameter type accepts int a[2][3][4] passed as just a?

    • A. int ***p
    • B. int (*p)[3][4]
    • C. int **p
    • D. int *p
    Show answer

    B. The array name decays to a pointer to a 3 by 4 block. A 3D array is never an int ***.

  10. With int (*p)[4] = m; (m as in question 5) and then p++, what is p[0][0]?

    • A. 10
    • B. 11
    • C. 20
    • D. 30
    Show answer

    C. p++ moves one whole row, so p now points at row 1 and p[0][0] is 20.

Programming Challenges

  1. Write int max_in_column(int (*m)[4], size_t rows, size_t col) and test it on a 3 by 4 table. Hint: loop over rows and keep the biggest m[r][col].
  2. Print the transpose of a 3 by 4 array using only the pointer form *(*(a+i)+j). Hint: the transpose has 4 rows and 3 columns, so swap the indexes when printing.
  3. Store a 2 by 2 by 3 table in a flat buffer and write board_max using the IDX macro from lecture 28. Hint: fix the board index, loop over channels and samples.

Interview Questions on Pointers, Arrays and Functions

1. What is the difference between int (*p)[4] and int *p[4]?

The first is one pointer to an array of 4 ints, so p + 1 moves 16 bytes with 4-byte ints. The second is an array of 4 pointers to int, which occupies 4 pointer sizes.

2. Why do arr and arr[0] hold the same address but arr + 1 and arr[0] + 1 differ?

Both start at the first byte, but arr points to a whole row while arr[0] points to a single int. Pointer arithmetic scales by the size of the pointed-to type.

3. Is a 2D array the same as an int **?

No. A 2D array is one contiguous block, and its name decays to a pointer to a row. An int ** points to a pointer, which is a different layout and cannot be used interchangeably.

4. How does the compiler find the address of a[i][j][k]?

It computes start + ((i x C + j) x S + k) x sizeof(element), where C and S are the second and third dimensions. The first dimension is not needed.

5. Why must a function parameter give all dimensions except the first?

The later dimensions determine the size of each step. Without them the compiler cannot compute the address of an element.

6. Does C support call by reference?

No. C is strictly call by value. You simulate call by reference by passing an address, and the address itself is copied.

7. Why does swap(int a, int b) not swap the caller’s variables?

The function receives copies. It swaps the copies, and they disappear on return. You need swap(int *a, int *b).

8. Why can you write p++ but not arr++ for an array arr?

An array name is not a modifiable lvalue; it denotes a fixed block of memory. A pointer is a normal variable, so it can be changed.

9. Why print addresses with %p instead of %u or %d?

An address may be larger than an unsigned int, such as 64 bits on modern systems. %p with a void * cast is the correct format, while %u or %d is undefined behaviour.

10. When would you flatten a multi-dimensional array into a 1D buffer in firmware?

When you want simple function signatures, one length for bounds checks, or easy DMA and memcpy. An index macro keeps the same row-major layout and the code stays readable.

Frequently Asked Questions

What is call by value in C?

It is the rule that a function receives copies of its arguments. Changes made to the parameters stay inside the function.

Does C have call by reference?

Not as a language feature. You pass the address of a variable, and the function uses it to reach the original. The address itself is still passed by value.

Why did my function not change the variable I passed?

Because only a copy was passed. Pass the address with & and dereference with * inside the function, or return the new value.

Can a function change the elements of an array I pass?

Yes. The array name decays to a pointer, so the function works on the original elements, not a copy.

Should I pass structs by value in embedded C programming?

Small ones are fine. For large structs prefer const pointers, because copying wastes stack space and time, which is scarce on small microcontrollers.

What is the difference between actual and formal parameters?

Actual parameters are the values in the call. Formal parameters are the variables in the function definition that receive the copies.

Finish the Free C Tutorial

You have completed this set of lectures in our free C programming course for embedded systems. Continue with pointers as function arguments, or return to the course index to plan your next topic.

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 *