Module 7 · Functions, Scope and Storage Classes
Functions: Giving a Piece of Work a Name
In this lesson
- Define a function above
main, and call it frommainby its name. - Explain why a program is split into functions: one job, one name, written once.
- Trace a call: control jumps into the function, runs its body, and comes back to the next line.
Remember Maria's till from Module 3? Her VAT rate was typed in eleven places, and one copy stayed wrong for three weeks.
Her receipt program now prints a sale slip, a refund slip and a closing report. Each starts with the same two-line header, copied three times.
When the VAT rate moved to 15 percent, Bob updated every copy he could find. He found two. Copying code is fast; finding every copy later is not.
This lesson gives that header one name and one home.
One piece of work, one name
Here is the program Bob edited, cut down to the three slips. In the real till, the copies were hundreds of lines apart.
#include <stdio.h>
int main(void)
{
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
printf("sale: 40\n");
printf("\n");
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
printf("refund: 50\n");
printf("\n");
printf("Maria General Store\n");
printf("VAT 7.5 percent included\n");
printf("day total: 1250\n");
return 0;
}
Maria General Store
VAT 15 percent included
sale: 40
Maria General Store
VAT 15 percent included
refund: 50
Maria General Store
VAT 7.5 percent included
day total: 1250
The closing report still says 7.5 percent. No compiler can catch it, because every line is valid C.
Maria asks her usual question: "But why not just be careful and change all three?"
Because the next person to edit the till will not know there are three. Care does not help when you cannot see the copies.
The fix is to write the header once, give it a name, and use the name three times. A named piece of work like this is a function. Running it by writing its name is called calling the function.
#include <stdio.h>
void print_header(void)
{
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
}
int main(void)
{
print_header();
printf("sale: 40\n");
printf("\n");
print_header();
printf("refund: 50\n");
printf("\n");
print_header();
printf("day total: 1250\n");
return 0;
}
Maria General Store
VAT 15 percent included
sale: 40
Maria General Store
VAT 15 percent included
refund: 50
Maria General Store
VAT 15 percent included
day total: 1250
Same shape, and all three headers are right. The VAT line now lives in one place, so the next change reaches every slip.
Now read main on its own: header, sale; header, refund; header, day total. It tells the program's story, with the details moved out of the way.
So a function gives one piece of work one name. You write it once, and you call it wherever that work is needed.
The anatomy of a definition
The block that creates a function is its definition. Here is the one above, part by part.
A function definition, and a call
void print_header(void)
{
statements
}
print_header();
voidbefore the name is the return type: what the function hands back when it ends.voidmeans nothing.print_headeris the name. It follows the same rules as a variable's name.(void)is the parameter list: what the function needs from its caller.voidhere means it takes nothing.- The braces hold the body, the statements that run every time the function is called.
print_header();is a call: the name, then(), then a semicolon.
A definition alone does nothing: the body runs only when the function is called, once per call.
So void print_header(void) reads: a function named print_header that takes nothing and hands nothing back. Lesson 2 puts real types in both places.
A call jumps in and comes back
What happens when main reaches a call? Here is a small program with two calls.
#include <stdio.h>
void print_header(void)
{
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
}
int main(void)
{
printf("till on\n");
print_header();
printf("sale: 40\n");
print_header();
printf("till off\n");
return 0;
}
till on
Maria General Store
VAT 15 percent included
sale: 40
Maria General Store
VAT 15 percent included
till off
At any moment, a running program is on exactly one line. Programmers say control is on that line. The program starts in main, even though print_header is written first.
Inside main, control moves down one line at a time. A call sends it to the first line of the function's body. At the body's closing brace, control comes back to the line right after the call.
The same run as a table, one row per line. The circled numbers match the diagram.
| Step | Line running | What prints |
|---|---|---|
| 1 | printf("till on\n"); in main | till on |
| 2 | the first print_header();, jump ① | nothing yet |
| 3 | the first printf of the body ② | Maria General Store |
| 4 | the second printf of the body ② | VAT 15 percent included |
| 5 | the body's closing brace, jump ③ | nothing |
| 6 | printf("sale: 40\n"); in main | sale: 40 |
| 7 | the second print_header();, jump ④ | nothing yet |
| 8 | the first printf of the body again ⑤ | Maria General Store |
| 9 | the second printf of the body again ⑤ | VAT 15 percent included |
| 10 | the body's closing brace, jump ⑥ | nothing |
| 11 | printf("till off\n"); in main | till off |
| 12 | return 0; in main | nothing: the program ends |
The body's two lines ran twice but are written once. main never lost its place: each time, it carried on after the call.
So a call is a jump with a return ticket. Control goes in, runs the whole body, and comes back to the next line.
main is a function too
int main(void) is a function definition too, the same shape as print_header.
int says it hands back a whole number. But who calls main? Nothing in your file does. The operating system (Linux, Windows, the software that runs every other program) calls it when the program starts.
Its return value becomes the program's exit code, which Module 1 called the exit status. 0 means "finished cleanly", and any other number means something went wrong.
#include <stdio.h>
int main(void)
{
printf("receipt printed\n");
return 3;
}
receipt printed
Run on GCC 12, the program prints its line and ends with exit code 3. On the Playground, the output panel shows Exit: 3, and the badge reads Runtime error. The printing went fine, but the 3 said something failed.
A judge reads 0 as a clean finish, so every program in this track ends with return 0;. Module 1 showed that main may skip that line. The brain teaser asks why only main may.
So main is an ordinary function with one special caller.
A function can call another function
A function's body can hold any statement main can hold, calls included. Here, print_receipt builds a whole receipt from two smaller functions.
#include <stdio.h>
void print_header(void)
{
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
}
void print_footer(void)
{
printf("Thank you, come again\n");
}
void print_receipt(void)
{
print_header();
printf("tea: 40\n");
print_footer();
}
int main(void)
{
print_receipt();
printf("till off\n");
return 0;
}
Maria General Store
VAT 15 percent included
tea: 40
Thank you, come again
till off
Follow control through it, counting the jumps as the diagram did.
maincallsprint_receipt: jump 1.print_receiptcallsprint_header: jump 2. The header prints.print_headerends: jump 3, back intoprint_receipt, which printstea: 40.print_receiptcallsprint_footer: jump 4. The thank-you line prints.print_footerends: jump 5, back intoprint_receipt.print_receiptends: jump 6, back intomain, which printstill off.
Every jump in has a matching jump back. The last function called is the first to finish; Module 8 draws this as a stack.
Notice that print_header and print_footer sit above print_receipt, which calls them. The next section explains why.
So functions can be built from functions, and control always comes back the way it went.
Define it before you call it
The compiler reads your file once, top to bottom. At each call, it must already know the function. The Playground compiles one file, so everything lives in that file, in order.
Bob likes main at the top, where the program starts. So he moves print_header below it.
#include <stdio.h>
int main(void)
{
print_header();
printf("sale: 40\n");
return 0;
}
void print_header(void)
{
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
}
The Playground prints two warnings and runs the program anyway:
warning: implicit declaration of function 'print_header' [-Wimplicit-function-declaration]warning: conflicting types for 'print_header'; have 'void(void)'
A declaration tells the compiler a function's name and shape before its first use. Bob's call came first, so GCC 12 guessed, using a rule from 1989 C: an unknown function returns an int. That guess is an implicit declaration.
Then the compiler met the real definition, which returns void, and the second warning reports the clash. The program runs here only by luck. GCC 14 stops guessing: the first warning becomes error: implicit declaration of function 'print_header' [-Wimplicit-function-declaration], and the build fails.
So the rule for now: define every function above the first line that calls it. Lesson 2 adds prototypes, one-line declarations that let main go first again.
A first look at a parameter
So far, a function did the same thing on every call. Kenji wants lines of different lengths from one function.
#include <stdio.h>
void draw_line(int width)
{
for (int i = 0; i < width; i++) {
printf("-");
}
printf("\n");
}
int main(void)
{
draw_line(10);
printf("SCORES\n");
draw_line(6);
return 0;
}
----------
SCORES
------
int width is a parameter: a variable of the function that gets its value from the call. The value written in the call is the argument. draw_line(10) runs the body with width equal to 10, and draw_line(6) runs it again with 6.
A function can take several parameters, separated by commas, as in void print_row(int i, int n). Lesson 2 explains exactly how a value travels from the call into the function. Exercises 2 and 3 need only this much.
So a parameter lets one function do its job with a different value on each call.
Naming a function
A function's name is a promise about what it does. Use a verb phrase in lower case, with words joined by underscores: print_header, draw_line, read_score. This style is called snake_case.
A function that answers a yes-or-no question starts with is_, like is_leap in lesson 2. Names like f1 or stuff make the reader open the body to learn anything. A good name lets them skip it.
printf and scanf are functions too, from the C standard library, which every C installation includes. #include <stdio.h> introduces them to the compiler before your first call.
So make the name say what the function does.
The smallest useful function: one line, called twice from main.
#include <stdio.h>
void say_hello(void)
{
printf("Hello from a function\n");
}
int main(void)
{
say_hello();
say_hello();
return 0;
}
Hello from a function
Hello from a function
Ten calls would print ten lines, and the function would still be written once.
Run in CompilerThree receipts, each built from the same header and footer. Only the item line changes.
#include <stdio.h>
void print_header(void)
{
printf("Maria General Store\n");
printf("VAT 15 percent included\n");
}
void print_footer(void)
{
printf("Thank you, come again\n");
printf("---------------------\n");
}
int main(void)
{
print_header();
printf("tea x 2: 40\n");
print_footer();
print_header();
printf("cake x 1: 50\n");
print_footer();
print_header();
printf("rice x 3: 360\n");
print_footer();
return 0;
}
Maria General Store
VAT 15 percent included
tea x 2: 40
Thank you, come again
---------------------
Maria General Store
VAT 15 percent included
cake x 1: 50
Thank you, come again
---------------------
Maria General Store
VAT 15 percent included
rice x 3: 360
Thank you, come again
---------------------
A new shop name is now a one-line change in print_header. Only the item lines stay in main.
Kenji's game prints a title, one line per round, and a total. It reads scores to the end of input, the pattern from Module 6.
#include <stdio.h>
void draw_line(int width)
{
for (int i = 0; i < width; i++) {
printf("=");
}
printf("\n");
}
int main(void)
{
int score = 0;
int round = 0;
int total = 0;
draw_line(20);
printf("KENJI'S SCOREBOARD\n");
draw_line(20);
while (scanf("%d", &score) == 1) {
round++;
total += score;
printf("round %d: %d\n", round, score);
}
draw_line(20);
printf("total: %d\n", total);
return 0;
}
====================
KENJI'S SCOREBOARD
====================
round 1: 120
round 2: 85
round 3: 240
====================
total: 445
That output is for the input 120 85 240. With an empty input, the title and total: 0 still print. draw_line(20) is called three times, so a new line style is one edit.
Where this is used
- The C standard library.
printfandscanfare functions like yours, written once by the library's authors. Every C program that prints something calls them. - The Linux kernel. It is built from many thousands of small named functions. It cannot use
printf, so it has its own printing function,printk, which writes to the kernel's log. - SQLite. A program uses this database by calling C functions such as
sqlite3_openandsqlite3_exec. - A typical game loop. It calls a drawing function, often named something like
draw_frame, once per frame, many times a second.
Common mistakes
1. Calling a function that is defined further down.
int main(void)
{
print_header();
printf("tea: 40\n");
print_footer();
return 0;
}
void print_footer(void)
{
printf("Thank you, come again\n");
}
The Playground prints warning: implicit declaration of function 'print_footer'; did you mean 'print_header'? [-Wimplicit-function-declaration] and runs the program anyway. The conflicting types warning follows, and GCC 14 refuses the build. Move the definition above main. You will do this because a new function seems to belong at the bottom of the file.
2. A call without the parentheses.
print_header;
printf("tea: 40\n");
Silent on the Playground, and the header never prints. A local gcc -Wall on GCC 12 says warning: statement with no effect [-Wunused-value]. Write print_header();. The name alone only mentions the function; the parentheses call it. A bare name reads like an order, and the Playground does not object.
3. A semicolon after the first line of a definition.
void print_header(void);
{
printf("Maria General Store\n");
}
An error on every command line, the Playground included: error: expected identifier or '(' before '{' token. Delete the semicolon after (void). Every statement ends in a semicolon, so this line looks like it needs one too. It does not: the body follows it directly.
4. A function defined inside main.
int main(void)
{
void print_header(void)
{
printf("Maria General Store\n");
}
print_header();
printf("tea: 40\n");
return 0;
}
No message at any command line, even -Wall -Wextra, and it runs. GCC allows it as an extension, a feature that is not standard C. With -pedantic, GCC 12 says warning: ISO C forbids nested functions [-Wpedantic]. Clang 18, another compiler, stops with error: function definition is not allowed here. Move the function above main. It happens because, until today, everything you wrote lived inside main.
Maria's tea house had the same trouble: three copies of the receipt banner, and one went stale. Now one function prints it.
Input. Sale amounts in taka, whole numbers, to the end of input. There may be none.
Output. For each amount, the two banner lines *** MARIA'S TEA HOUSE *** and VAT included, then total: <amount>. After the last receipt, receipts: <k>, where k counts the amounts (0 for an empty input). The judge sees only the output. Write the banner as void print_banner(void).
Constraints. At most 1000 amounts, each from 1 to 1000000.
Sample. Input 120 45 gives these seven lines.
*** MARIA'S TEA HOUSE ***
VAT included
total: 120
*** MARIA'S TEA HOUSE ***
VAT included
total: 45
receipts: 2
#include <stdio.h>
void print_banner(void)
{
/* Print the two banner lines. */
}
int main(void)
{
int amount = 0;
while (scanf("%d", &amount) == 1) {
/* Call print_banner, then print the total line. */
}
/* Print "receipts: k", where k counts the amounts read. */
return 0;
}
Graded as receipt-banner. The hidden tests include an empty input and 1000 receipts.
Zara makes times-table cards for a maths club. One function prints one row, and main calls it once for each row.
Input. One integer n.
Output. n lines. Line i holds i x 1 up to i x n, one space between two numbers. Write void print_row(int i, int n) to print one line.
Constraints. 1 <= n <= 12.
Sample. Input 3 gives these three lines.
1 2 3
2 4 6
3 6 9
#include <stdio.h>
void print_row(int i, int n)
{
/* Print i*1, i*2, and so on up to i*n, one space between two numbers. */
}
int main(void)
{
int n = 0;
scanf("%d", &n);
/* Call print_row once for each i from 1 to n. */
return 0;
}
Not graded on its own. Module 6's aligned-table already grades the table, and receipt-banner and star-box grade the calls.
Kenji's game draws a frame of stars around each score panel. One function draws a whole box.
Input. Pairs width height to the end of input, at least one pair.
Output. For each pair, a hollow box. The first and the last row are width stars. Every row in between is a star, width - 2 spaces and a star. A box 1 wide is a column of single stars, and a box 1 high is one row. Print one empty line between two boxes, none before the first.
The judge sees only the output. Write void draw_box(int width, int height). Helpers such as void draw_full_row(int width) and void draw_hollow_row(int width) are welcome.
Constraints. 1 <= width, height <= 50. At most 20 pairs.
Sample. Input 4 3 then 1 2 gives these six lines.
****
* *
****
*
*
#include <stdio.h>
void draw_box(int width, int height)
{
/* Full rows at the top and the bottom, a star at each end
of every row in between. What if width or height is 1? */
}
int main(void)
{
int width = 0;
int height = 0;
while (scanf("%d %d", &width, &height) == 2) {
/* Print one empty line before every box except the first. */
draw_box(width, height);
}
return 0;
}
Graded as star-box. The hidden tests include boxes 1 wide and 1 high, and the largest box.
Common doubts
Does a function make the program slower?
Kenji asks this first. Not by anything you could notice. At
-O2, the Playground's setting, GCC 12 copies a tiny function's body into its caller, which is called inlining. Example 1'smainends up printing both lines itself and never callssay_hello. The biggerprint_headerkeeps its calls, and each jump costs a few machine instructions.Can my own code call
main?In C it is legal. Nobody does it, because it starts the program's story again from the top. Leave
mainto the operating system.Why is
voidwritten twice?They answer two questions. The one in front says what comes back: nothing. The one inside says what goes in: nothing. Lesson 2 shows why this track writes the second one, even though empty
()also compiles.Can two functions have the same name?
Not in one program. A second
print_headergiveserror: redefinition of 'print_header'on every command line. Choose names that say how the two differ, such asprint_shop_headerandprint_report_header.
Key takeaways
- A function is a named piece of work: write it once, call it wherever it is needed.
- A definition has a return type, a name, a parameter list and a body.
voidin front hands back nothing;(void)takes nothing. - A call jumps into the function, runs its whole body, and comes back to the line after the call.
mainis a function the operating system calls, and its return value is the exit code a judge reads.- Define a function above the first line that calls it. GCC 12 only warns about the other order, and GCC 14 refuses it.
- Name a function with a verb phrase in snake_case, such as
print_headerordraw_line.
Next, functions take values in and hand a value back, and prototypes let main come first.
End of lesson 1
Mark it done, and your progress moves with you.
Next: Parameters, Return Values and Prototypes