Nearby lessons

74 of 124

C - Function Calling

Calling a function transfers control to it, runs its body, and brings the result back. Learn call syntax, using the returned value, nested and chained calls, and how the call stack keeps track of it all.

The Basic Call

Write the name, then the arguments in parentheses. The parentheses are mandatory — without them you get the function's address, not a call:

Example01
CCode Cell
1#include <stdio.h>
2 
3void sayHello(void) { printf("Hello!\n"); }
4int add(int a, int b) { return a + b; }
5 
6int main()
7{
8 sayHello(); /* no arguments, still needs () */
9 printf("%d\n", add(3, 4)); /* two arguments */
10 
11 /* sayHello; does nothing - just names the function */
12 return 0;
13}
Output
Hello!
7

A Call Is an Expression

Because a non-void call produces a value, it can go anywhere a value can go:

Example02
CCode Cell
1#include <stdio.h>
2 
3int square(int n) { return n * n; }
4 
5int main()
6{
7 int x = square(4); /* assign it */
8 int y = square(3) + square(2); /* use in arithmetic */
9 int a[3] = {square(1), square(2), square(3)}; /* initialise */
10 
11 printf("x = %d\n", x);
12 printf("y = %d\n", y);
13 printf("array: %d %d %d\n", a[0], a[1], a[2]);
14 
15 if (square(5) > 20) /* use in a condition */
16 printf("square(5) exceeds 20\n");
17 return 0;
18}
Output
x = 16
y = 13
array: 1 4 9
square(5) exceeds 20

Nested Calls

An argument can itself be a call. C evaluates the inner call first and feeds the result outward:

Example03
CCode Cell
1#include <stdio.h>
2 
3int add(int a, int b) { return a + b; }
4int square(int n) { return n * n; }
5int half(int n) { return n / 2; }
6 
7int main()
8{
9 printf("square(add(2, 3)) = %d\n", square(add(2, 3)));
10 printf("add(square(2), square(3)) = %d\n", add(square(2), square(3)));
11 printf("half(square(add(1, 3))) = %d\n", half(square(add(1, 3))));
12 return 0;
13}
Output
square(add(2, 3))       = 25
add(square(2), square(3)) = 13
half(square(add(1, 3))) = 8

The Call Stack

In simple words: each call pushes a frame onto the stack holding that call's parameters, locals, and the address to return to. When the function returns, its frame is popped and the caller continues. That is why every call has its own private copy of its locals.
Example04
CCode Cell
1#include <stdio.h>
2 
3void level3(void) { printf(" level3 running\n"); }
4void level2(void) { printf(" level2 start\n"); level3(); printf(" level2 end\n"); }
5void level1(void) { printf("level1 start\n"); level2(); printf("level1 end\n"); }
6 
7int main()
8{
9 level1();
10 printf("back in main\n");
11 return 0;
12}
Output
level1 start
  level2 start
    level3 running
  level2 end
level1 end
back in main

Ignoring the Return Value

C lets you discard a return value. Sometimes that is fine; sometimes it hides a failure you needed to know about:

Example05
CCode Cell
1#include <stdio.h>
2 
3int saveData(int value)
4{
5 if (value < 0) return 0; /* 0 signals failure */
6 printf("Saved %d\n", value);
7 return 1; /* 1 signals success */
8}
9 
10int main()
11{
12 saveData(10); /* result ignored - it worked anyway */
13 
14 saveData(-5); /* failure silently discarded! */
15 
16 if (!saveData(-5)) /* better: check it */
17 printf("Save failed for -5\n");
18 return 0;
19}
Output
Saved 10
Save failed for -5

Arguments Are Copies

By default C passes arguments by value — the function works on copies, so the caller's variables are untouched:

Example06
CCode Cell
1#include <stdio.h>
2 
3void tryToChange(int n)
4{
5 n = 999; /* changes the local copy only */
6 printf("Inside : n = %d\n", n);
7}
8 
9int main()
10{
11 int x = 10;
12 
13 printf("Before : x = %d\n", x);
14 tryToChange(x);
15 printf("After : x = %d\n", x); /* still 10 */
16 return 0;
17}
Output
Before : x = 10
Inside : n = 999
After  : x = 10

Calling in a Loop

A function called in a loop runs fresh each iteration — this is where reuse really pays off:

Example07
CCode Cell
1#include <stdio.h>
2 
3int isPrime(int n)
4{
5 int i;
6 if (n < 2) return 0;
7 for (i = 2; i * i <= n; i++)
8 if (n % i == 0) return 0;
9 return 1;
10}
11 
12int main()
13{
14 int n;
15 
16 printf("Primes up to 30: ");
17 for (n = 2; n <= 30; n++)
18 if (isPrime(n))
19 printf("%d ", n);
20 printf("\n");
21 return 0;
22}
Output
Primes up to 30: 2 3 5 7 11 13 17 19 23 29 

A Function Calling Itself

Recursion is just a function calling itself. Each call gets its own frame, and a base case stops the descent:

Example08
CCode Cell
1#include <stdio.h>
2 
3int factorial(int n)
4{
5 if (n <= 1) return 1; /* base case */
6 return n * factorial(n - 1); /* recursive call */
7}
8 
9int main()
10{
11 int i;
12 for (i = 1; i <= 6; i++)
13 printf("%d! = %d\n", i, factorial(i));
14 return 0;
15}
Output
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720

Common Mistakes

  • Omitting the parenthesessayHello; compiles but calls nothing.
  • Wrong argument count — passing two arguments to a one-parameter function is a compile error.
  • Arguments in the wrong orderdivide(2, 10) instead of divide(10, 2) compiles and gives the wrong answer.
  • Expecting the caller's variable to change — pass a pointer if the function must modify it.
  • Ignoring a status return — a silently failed fopen or scanf is a hard bug to trace.
  • Recursion with no base case — the stack fills and the program crashes.
Wrong argument order is the bug compilers cannot catch. If both parameters are int, divide(2, 10) is perfectly valid code that returns 0 instead of 5. Name parameters clearly, keep the count small, and consider a struct when a function needs more than three or four related inputs.
📝 Key Takeaways
  • Syntax: functionName(arguments); — parentheses are required even when empty.
  • A call to a non-void function is an expression with a value.
  • Arguments may themselves be function calls.
  • Each call gets its own stack frame holding its locals.
  • Ignoring a return value is legal but often a bug.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4