C functions
last modified November 14, 2026
A function is a named block of statements with a return type, a parameter list
and a body. Calling the function transfers control to the body, binds the
arguments to the parameters and returns a value to the caller. C programs are
built from functions: main is the entry point, the standard library
supplies ready-made functions, and the programmer writes the rest. This tutorial
covers definitions and prototypes, return values, void functions,
pass by value, array parameters, recursion and static functions.
Defining and calling a function
A function definition has a return type, a name, a parameter list and a body.
The declaration above main, called a prototype, repeats the return
type and parameter types and ends with a semicolon. The prototype lets the
compiler check calls that appear before the definition; without it, the compiler
has no declaration to check and C17 rejects the implicit declaration, while some
compilers accept it only as an extension with a warning. The definition itself
follows main and supplies the body.
#include <stdio.h>
int square(int x);
int main(void) {
int n = 7;
printf("%d squared is %d\n", n, square(n));
printf("%d squared is %d\n", 12, square(12));
return 0;
}
int square(int x) {
return x * x;
}
The call square(n) copies the value of n into the
parameter x, multiplies the copy by itself and returns 49. The
second call uses the constant 12 and does not touch n at all. A
function can be called as many times as needed, and each call gets a fresh set
of parameters.
$ gcc -std=c17 -Wall -Wextra -o function_basic function_basic.c $ ./function_basic 7 squared is 49 12 squared is 144
Returning a value
A return statement ends the function immediately and supplies the
value of the expression that follows it. A function may contain several return
statements, but every path that can be reached must return a value compatible
with the declared return type. The next example computes the largest of three
ints with a local variable and two comparisons, then returns that variable.
#include <stdio.h>
int max3(int a, int b, int c) {
int max = a;
if (b > max) {
max = b;
}
if (c > max) {
max = c;
}
return max;
}
int main(void) {
printf("max3(3, 9, 4) = %d\n", max3(3, 9, 4));
printf("max3(-5, -2, -8) = %d\n", max3(-5, -2, -8));
return 0;
}
The parameter list (int a, int b, int c) declares three separate
copies, so the function can compare them without changing the caller's values.
max starts as a, is replaced by b when
b is larger, and is replaced by c when c
is larger still. The result is returned as an int and printed
directly in printf.
$ gcc -std=c17 -Wall -Wextra -o function_max function_max.c $ ./function_max max3(3, 9, 4) = 9 max3(-5, -2, -8) = -2
Functions that return nothing
A function whose only purpose is an effect on the outside world, such as
printing, has the return type void. It does not return a value, and
return; is allowed but optional. The parameters still have types:
const char *title accepts a string literal and promises not to
modify it. The two functions below print a heading and a sum, and
main calls them for their side effects.
#include <stdio.h>
void print_line(const char *title) {
printf("== %s ==\n", title);
}
void print_sum(int a, int b) {
printf("%d + %d = %d\n", a, b, a + b);
}
int main(void) {
print_line("totals");
print_sum(4, 5);
print_sum(-3, 8);
return 0;
}
print_line receives the address of the first character of the
literal and prints it with %s. print_sum receives two
copies of its arguments and prints their sum. Neither call produces a usable
value, so the calls appear as statements on their own rather than inside an
expression.
$ gcc -std=c17 -Wall -Wextra -o function_void function_void.c $ ./function_void == totals == 4 + 5 = 9 -3 + 8 = 5
Pass by value
C passes every argument by value: the function receives a copy, and assigning to
a parameter changes only that copy. A function that must modify the caller's
variable therefore receives its address, a pointer, and dereferences the pointer
to reach the original object. The & operator produces an
address and the unary * operator reads or writes the object at that
address. The next example shows both the failed attempt and the working version.
#include <stdio.h>
void swap_wrong(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 3;
int y = 8;
swap_wrong(x, y);
printf("after swap_wrong: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("after swap: x = %d, y = %d\n", x, y);
return 0;
}
swap_wrong swaps its own parameters, which disappear when it
returns, so x and y keep their values. swap
receives the addresses of the two variables, and its assignments to
*a and *b change the objects in main. The
output shows the difference: the first call leaves 3 and 8, the second produces
8 and 3.
$ gcc -std=c17 -Wall -Wextra -o function_swap function_swap.c $ ./function_swap after swap_wrong: x = 3, y = 8 after swap: x = 8, y = 3
Arrays as parameters
An array argument is not copied. It is converted to a pointer to its first
element, so a function parameter written as double values[] behaves
like double *values, and the function can read or modify the
caller's elements. The length is not part of the pointer, so it must be passed
as a separate parameter. A const pointer documents that the
function only reads the array.
#include <stdio.h>
double average(const double values[], size_t len) {
double sum = 0.0;
for (size_t i = 0; i < len; i++) {
sum += values[i];
}
return len == 0 ? 0.0 : sum / (double) len;
}
void scale(double values[], size_t len, double factor) {
for (size_t i = 0; i < len; i++) {
values[i] *= factor;
}
}
int main(void) {
double data[] = { 2.0, 4.0, 6.0, 8.0 };
size_t len = sizeof data / sizeof data[0];
printf("mean: %.2f\n", average(data, len));
scale(data, len, 0.5);
printf("after scale: %.2f %.2f %.2f %.2f\n",
data[0], data[1], data[2], data[3]);
return 0;
}
average takes a const pointer and a length, adds the elements and
divides by the count. scale takes a non-const pointer and
multiplies every element in place, so the change is visible in main
after the call. The length is computed once in main with
sizeof and passed to both functions; neither function can recover
it from the pointer.
$ gcc -std=c17 -Wall -Wextra -o function_array function_array.c $ ./function_array mean: 5.00 after scale: 1.00 2.00 3.00 4.00
Recursion
A recursive function calls itself. Every recursion needs at least one base case
that returns without another call, and a recursive case that moves the argument
toward the base case. The factorial of n is the product of all
integers from 1 to n, with 0! = 1; the recursive
definition multiplies n by the factorial of n - 1. The
next program uses unsigned long long because factorials grow
quickly.
#include <stdio.h>
unsigned long long factorial(unsigned int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int main(void) {
for (unsigned int i = 0; i <= 10; i++) {
printf("%u! = %llu\n", i, factorial(i));
}
return 0;
}
When n is 0 or 1 the base case returns 1 immediately. For larger
values each call waits for the next one, and the multiplications happen as the
calls return: 10! is 3628800. The recursion depth equals n here, so
a very large argument would exhaust the call stack; the result would also
overflow unsigned long long long before that.
$ gcc -std=c17 -Wall -Wextra -o function_recursion function_recursion.c $ ./function_recursion 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800
Static functions
The static keyword on a function gives it internal linkage. The
function is visible throughout its own source file but not to other translation
units, so another .c file cannot call it and may define a function
with the same name without a conflict. Helper functions that are not part of a
module's public interface are good candidates for static. The
functions below are both local to this file.
#include <stdio.h>
static int square(int x) {
return x * x;
}
static int cube(int x) {
return x * square(x);
}
int main(void) {
for (int i = 2; i <= 4; i++) {
printf("%d: square = %d, cube = %d\n", i, square(i), cube(i));
}
return 0;
}
square and cube are ordinary functions inside the
file; static only changes their linkage. cube calls
square, and main calls both for 2, 3 and 4. If
another file in the same program also defined square, the linker
would keep the two definitions separate because neither is exported.
$ gcc -std=c17 -Wall -Wextra -o function_static function_static.c $ ./function_static 2: square = 4, cube = 8 3: square = 9, cube = 27 4: square = 16, cube = 64
Source
cppreference: function declaration
cppreference: return statement
cppreference: storage duration and linkage
cppreference: function call operator
This tutorial covered functions in C: definitions and prototypes, return values,
void functions, pass by value and pointer parameters, arrays as
parameters with an explicit length, recursion with a base case, and
static functions with internal linkage. The
C arrays tutorial explains the array-to-pointer
conversion used by array parameters, C scope and storage
classes covers the lifetime of the objects a function works with, and
C header files shows how to share prototypes
between source files.
Author
List all C tutorials.