C · Beginner
Functions in C
In short: A C function is a named block with a return type, a parameter list and a body, such as int area(int w, int h) { return w * h; }. A call copies the argument values into the parameters, runs the body and hands back the return value; changes to a parameter never reach the caller's variable. A function must be declared, by a prototype or its full definition, before the line that calls it.
Naming a piece of work
main can hold an entire program, but a program that does three things in one long block is hard to read, hard to test and impossible to reuse. A function packages one job under a name: what goes in (the parameters), what comes out (the return value) and how it is done (the body). The caller only needs to know the first two.
A definition has four parts: the return type, the name, the parameter list in parentheses and the body in braces. int fare_pence(int zones, int peak) returns an int and takes two int parameters. Inside the body, return expression; ends the function and delivers the value; a function may have several return statements, and returning early from a guard clause is often clearer than nesting the rest in an else. A function that produces nothing has return type void and either ends at its closing brace or uses a bare return;. A function that takes nothing is written with (void) in its parameter list, as main is.
Calling a function is writing its name with arguments in parentheses: fare_pence(2, 1). C copies each argument value into the corresponding parameter, in order, and the parameters are ordinary local variables of the function. This is pass by value: assigning to a parameter changes the copy and nothing else. To give the caller a result, return it; to let a function change the caller's variable directly, you pass its address, which is the subject of the pointers lesson. Arrays are the exception you meet first; see arrays.
C reads a file top to bottom, and it must know a function's return type and parameter types before it can compile a call. Either define the function above the call, or put a prototype above it: the first line of the definition followed by a semicolon, int fare_pence(int zones, int peak);. Prototypes let main sit at the top where readers look for it, and let two functions call each other. gcc 13 warns about a call to an undeclared function and gcc 14 refuses to compile it.
Variables declared inside a function, parameters included, are local: they exist only during the call, and two functions may use the same name for different variables without conflict. Variables declared outside all functions are global, visible everywhere after their declaration, and worth avoiding until you have a reason: they make it impossible to tell from a call what a function touches.
Well-chosen functions read like a description of the program: read_readings, average, print_report. When a name needs the word "and", the function is doing two jobs; split it.
Syntax
return_type name(type param1, type param2); /* prototype: ends with ; */
return_type name(type param1, type param2) /* definition */
{
body;
return value; /* required unless return_type is void */
}
void name(void); /* takes nothing, returns nothing */
result = name(arg1, arg2); /* call: arguments are copied in */Parameter names in a prototype are optional (int fare_pence(int, int);) but keeping them documents the call.
Parts of a function
| Part | Example | Meaning |
|---|---|---|
| Return type | int | type of the value the call produces; void for none |
| Name | fare_pence | how the function is called |
| Parameters | (int zones, int peak) | local variables filled from the arguments |
| Body | { ... } | the statements that run on each call |
| return | return pence; | ends the call and delivers the value |
| Prototype | int fare_pence(int, int); | tells the compiler the signature before the definition |
Prototype, definition and call
Two functions are declared above main and defined below it; main calls them to price three bus tickets.
#include <stdio.h>
int fare_pence(int zones, int peak);
void print_fare(int zones, int peak, int pence);
int main(void)
{
print_fare(2, 0, fare_pence(2, 0));
print_fare(2, 1, fare_pence(2, 1));
print_fare(5, 1, fare_pence(5, 1));
return 0;
}
int fare_pence(int zones, int peak)
{
int pence = 150 + zones * 45;
if (peak) {
pence += 80;
}
return pence;
}
void print_fare(int zones, int peak, int pence)
{
printf("%d zone(s), %s: %d.%02d\n", zones, peak ? "peak" : "off-peak", pence / 100, pence % 100);
}
Output
2 zone(s), off-peak: 2.40 2 zone(s), peak: 3.20 5 zone(s), peak: 4.55
The two prototypes let main call fare_pence and print_fare although their definitions come later. Each call to fare_pence copies its arguments into fresh zones and peak variables, computes a price in pence and returns it; the result is passed straight on as the third argument of print_fare, which returns nothing and only prints. Keeping money in whole pence and splitting it with / 100 and % 100 avoids floating-point rounding; %s prints the string chosen by the conditional operator.
Arguments are copied
One function tries to change its parameter; the other returns a new value instead. Watch what happens to the caller's variable.
#include <stdio.h>
void try_to_double(int n)
{
n = n * 2;
printf("inside: n = %d\n", n);
}
int doubled(int n)
{
return n * 2;
}
int main(void)
{
int crates = 8;
try_to_double(crates);
printf("after call: crates = %d\n", crates);
crates = doubled(crates);
printf("after assignment: crates = %d\n", crates);
return 0;
}
Output
inside: n = 16 after call: crates = 8 after assignment: crates = 16
try_to_double doubles its own n and prints 16, but crates in main is still 8 afterwards: the function received a copy. The second function returns the doubled value instead, and main stores it with an assignment, which is the normal way to get a result out of a function.
Early returns and reuse
One function answers a question that has several cases; main calls it twice with different arguments. The input is 9 2024.
#include <stdio.h>
int days_in_month(int month, int year)
{
if (month == 2) {
int leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
return leap ? 29 : 28;
}
if (month == 4 || month == 6 || month == 9 || month == 11) {
return 30;
}
return 31;
}
int main(void)
{
int month, year;
if (scanf("%d %d", &month, &year) != 2) {
return 1;
}
printf("%d days\n", days_in_month(month, year));
printf("February %d has %d days\n", year, days_in_month(2, year));
return 0;
}
Input given to the program: 9 2024
Output
30 days February 2024 has 29 days
Each return ends the function immediately, so the checks read as a list of special cases followed by the general answer, with no else chains. The second call shows the payoff of a function: the leap-year logic is written once and used for any year. leap is an int holding 1 or 0 from the logical expression, then used by the conditional operator. Because the function is defined above main, no prototype is needed.
Common mistakes
Calling a function before the compiler has seen it
Why it goes wrong: With the definition below main and no prototype, gcc 13 reports
implicit declaration of functionand assumes it returns int, so a function that really returns double produces garbage; gcc 14 refuses to compile it.Fix: Add a prototype above the first call, or move the definition above main.
C · fixdouble average(int total, int count); /* prototype */ int main(void) { printf("%.1f\n", average(7, 2)); return 0; } double average(int total, int count) { return (double) total / count; }Expecting a function to change its argument
Why it goes wrong: Parameters are copies.
void halve(int n) { n = n / 2; }halves its own n and returns; the caller's variable keeps its value.Fix: Return the new value and assign it in the caller, or pass a pointer once you have met them.
C · fixint halved(int n) { return n / 2; } /* in the caller: */ stock = halved(stock);A non-void function without a return on some path
Why it goes wrong: If execution reaches the closing brace of a function that promises an int, the value the caller receives is undefined. gcc -Wall reports
control reaches end of non-void function.Fix: Make sure every path returns, usually by ending with an unconditional return.
C · fixint sign(int x) { if (x > 0) { return 1; } if (x < 0) { return -1; } return 0; }Prototype and definition that disagree
Why it goes wrong:
int scale(int v);above anddouble scale(double v) { ... }below are two different functions to the compiler, and it stops withconflicting types for 'scale'.Fix: Copy the definition's first line exactly, add a semicolon, and change both when the signature changes.
Splitting an exercise into steps
A typical exercise reads some numbers, computes something and prints a result. Written as separate functions, say read_values, compute and print_result, each piece can be checked on its own: call compute with values you typed in and print what it returns before wiring up the input. The compute function is also where the reasoning lives, free of scanf and printf clutter, which makes it easy to reread when the output is wrong. Later lessons on arrays and recursion rely on this habit: a function that takes an array and a length, or that calls itself, is only manageable when its job is small and named.
int shipping_pence(int weight_grams)
{
if (weight_grams <= 500) {
return 150;
}
return 150 + (weight_grams - 500 + 249) / 250 * 40;
}Key points
- Definition: return type, name, parameters, body. Call:
name(arguments). - Arguments are copied into the parameters; a function cannot change the caller's variables through them.
- Return a value with
return expr;; usevoidwhen there is nothing to return. - The compiler must see a prototype or the definition before the first call.
- Parameters and variables declared inside the body are local to each call.
- Several early returns are fine and often clearer than nested else blocks.
- Give each function one job and a name that says what it returns or does.
Try it yourself
Complete round_up_to so it returns the smallest multiple of step that is greater than or equal to value. main reads two integers, value and step (step is at least 1). For the input 47 10 the program prints 50.
#include <stdio.h>
int round_up_to(int value, int step)
{
/* return the smallest multiple of step that is >= value */
return 0;
}
int main(void)
{
int value, step;
if (scanf("%d %d", &value, &step) != 2) {
return 1;
}
printf("%d\n", round_up_to(value, step));
return 0;
}
47 1050#include <stdio.h>
int round_up_to(int value, int step)
{
return (value + step - 1) / step * step;
}
int main(void)
{
int value, step;
if (scanf("%d %d", &value, &step) != 2) {
return 1;
}
printf("%d\n", round_up_to(value, step));
return 0;
}
Practise this
Exercises for this lesson are in the C practice set.
Related lessons
- Loops in C: for, while and do-whileThe three C loops and when each fits: how a for loop's three parts run, reading input until it ends with while and scanf, and using break and continue safely.9 min
- Arrays in CHow C arrays store same-typed values by zero-based index: initialiser lists, the sizeof length idiom, passing arrays to functions and out-of-range dangers.10 min
- Pointers in CWhat a C pointer is, how & and * work, why functions take addresses to change their caller's variables, and how pointer arithmetic walks an array.10 min
- Recursion in CHow recursive functions work in C: base and recursive cases, what happens on the call stack, digit and array recursion, and merge sort as divide and conquer.10 min
Frequently asked questions
Why does C need function prototypes?
The compiler works through a file from top to bottom and generates code for a call as soon as it meets one. To do that correctly it must already know the return type and the parameter types, so that arguments are converted properly and the result is used with the right type. A prototype supplies that information without the body, which lets you order the file for readers and lets functions refer to each other. Header files such as stdio.h are largely collections of prototypes.
Can a C function return two values?
Not directly: a function has one return value. The usual routes are to return a struct that bundles the values (see structures), or to pass the addresses of variables the function should fill in, which is how scanf delivers several values at once and is covered in the pointers lesson.
Can I define a function inside another function?
Not in standard C. gcc accepts nested functions as an extension, but the code will not compile elsewhere, so keep every definition at file level. If you want a helper that only one function uses, define it above that function and add static in front to keep it private to the file.
How this page was checked. Every program on it was run with GCC 13 (C11) at build time by the publishing checks, and the output shown is what it printed. Running C inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C11) locally.