Skip to content

C lesson 1 of 8

Your First C Program

What every C program must contain - the main function, printf and its format specifiers, escape sequences, and what a compiler actually does to your source file before anything runs.

Published · Every example on this page was run before it was published.

C does not run your file. It translates it. Before a single character appears on screen, a separate program called a compiler reads your source text and turns it into machine instructions, and only that translated result is ever executed. That one fact explains most of what feels strange about C at the start: why every program needs a main, why you have to announce which library functions you intend to use, and why a misplaced semicolon stops you before anything runs at all. This lesson gets a program onto the screen, then explains each part of it.

The Smallest Complete Program

Here is a complete C program. Nothing has been left out.

C
#include <stdio.h>

int main(void) {
    printf("Hello from C.\n");
    return 0;
}
Output
Hello from C.

Four things are doing work there.

#include <stdio.h> is a line for the preprocessor, a step that runs before the compiler proper and does simple text editing on your file. This particular line pastes in the contents of the standard header stdio.h, which describes the standard input/output functions - printf among them. Without that line the compiler would reach printf and have no idea what it is or what arguments it takes. The # marks a preprocessor directive, and directives never end in a semicolon.

int main(void) declares a function named main. Every C program needs exactly one, because main is where execution begins - the compiled program starts at the first line of main and stops when main finishes. The int in front says main hands back an integer when it is done. The void inside the parentheses says this main takes no arguments; writing empty parentheses instead is legal but means something looser, so (void) is the clearer habit.

The braces { and } mark the beginning and end of the function's body: the statements that run, in order, from top to bottom.

return 0; ends main and hands the value 0 back to whatever started the program. By long convention 0 means "finished normally" and any other number means something went wrong. Every statement inside a function body ends with a semicolon - the semicolon is what separates one statement from the next, not the newline, so C does not care how you spread a statement across lines.

printf Prints Exactly What You Tell It To

printf writes text to the program's output. It does not add a line break of its own - if you want one, you put one in the text yourself, as \n.

C
#include <stdio.h>

int main(void) {
    printf("first");
    printf("second\n");
    printf("third\n");
    return 0;
}
Output
firstsecond
third

Three calls, two lines of output. The first two calls both wrote onto the same line because only the second one ended with \n. This trips up everyone once, and it is worth meeting on purpose now rather than wondering later why two pieces of a report ran together.

\n is an escape sequence: a backslash followed by another character, which together stand for one character that would be awkward or impossible to type inside quotes. The ones you will reach for constantly are \n for a newline, \t for a tab, \\ for a single literal backslash, and \" for a double quote inside a double-quoted string.

C
#include <stdio.h>

int main(void) {
    printf("A backslash: \\\n");
    printf("A double quote: \"\n");
    printf("Two lines from one call:\nhere is the second\n");
    return 0;
}
Output
A backslash: \
A double quote: "
Two lines from one call:
here is the second

The first line needed three backslashes in a row in the source: \\ produces one backslash, and the \n that follows produces the newline. Read escape sequences in pairs from left to right and they stop being confusing.

Format Specifiers: Printing Values, Not Just Text

The real job of printf is assembling text out of values. Inside the quoted string you leave a placeholder beginning with %, and you pass the value to fill it as a further argument after the string. The placeholder is called a format specifier, and which one you use depends on the type of the value.

C
#include <stdio.h>

int main(void) {
    int crates = 7;
    double mass = 12.5;
    char grade = 'B';

    printf("Crates: %d\n", crates);
    printf("Mass: %f\n", mass);
    printf("Mass: %.2f\n", mass);
    printf("Grade: %c\n", grade);
    printf("Label: %s\n", "dry goods");
    return 0;
}
Output
Crates: 7
Mass: 12.500000
Mass: 12.50
Grade: B
Label: dry goods

%d prints an int in decimal. %f prints a double, and on its own it always shows six digits after the point, which is why 12.5 came out as 12.500000. Writing %.2f instead asks for exactly two digits after the point. %c prints a single character - note that a single character is written between single quotes, 'B', while text of any length goes between double quotes. %s prints a string.

The next lesson covers what int, double and char actually mean. For now the point is that the specifier and the value have to agree: %d expects an int, %f expects a double, and handing one the other is a mistake the compiler will normally warn you about but is not obliged to reject.

One printf call can carry as many specifiers as you like. They are filled left to right from the arguments that follow, and %% prints a literal percent sign, since a lone % would be read as the start of another specifier.

C
#include <stdio.h>

int main(void) {
    int done = 18;
    int total = 24;

    printf("%d of %d tasks done (%.1f%%)\n", done, total, 100.0 * done / total);
    return 0;
}
Output
18 of 24 tasks done (75.0%)

Three specifiers, three arguments, in that order. The third argument is an expression rather than a plain variable, which is fine - printf receives whatever it evaluates to.

What the Compiler Actually Does

When you press Run on this site, your editor's contents are saved as a file named main.c and handed to a real C compiler: clang, built to target WebAssembly through Emscripten, invoked roughly as emcc -O0 -fexceptions main.c. The -O0 asks for no optimisation, so the machine code stays close to what you wrote. The result is WebAssembly that runs in this browser tab. On your own machine the same source would go through a compiler such as gcc or clang and come out as an executable file you could run from a terminal, but the stages are the same either way:

  1. Preprocessing. Every #include is replaced by the contents of that header, and every macro is expanded. What the compiler proper sees is a much longer file than the one you typed.
  2. Compiling. The expanded source is checked against the rules of the language and translated into machine instructions. This is the stage that reports errors.
  3. Linking. Your compiled code is joined to the compiled standard library, so the call you wrote to printf is connected to the real printf. A program that compiles cleanly can still fail here, if it calls something that exists nowhere.

C has had several official versions, and this site compiles with the default the toolchain ships with, C17. Everything in these lessons is ordinary C that has been valid for a long time, so it will build the same way with any modern compiler.

When It Does Not Compile

If the compiler cannot make sense of your file, nothing runs - not even the lines before the mistake, because translation never finished. Instead you get a diagnostic: a message that names the file, the line and the column it gave up at, in the form main.c:5:30:, followed by a description of the problem and a copy of the offending line with a caret under the exact spot.

Three mistakes produce almost all early diagnostics:

  • A missing semicolon. The compiler carries on reading, decides the next line is part of the same statement, and complains about something that looks fine. When a message points at a line you are sure about, check the line above it.
  • A missing #include. Calling printf with no <stdio.h> means the compiler has never heard of the name. The message will talk about an undeclared function, not about printf being wrong.
  • Unbalanced braces or parentheses. One missing } usually produces a complaint at the very end of the file, because that is where the compiler finally runs out of text.

Read diagnostics from the top down and fix the first one before looking at the rest; a single early mistake often generates a cascade of later complaints that vanish on their own.

The Return Value of main

return 0; is a message to whoever launched the program. Change it to return 3; and the program still prints everything it printed before, but this site adds a line of its own underneath the output: The program exited with status 3. That is not your program talking - it is the runner reporting the number main handed back. Nonzero means "something went wrong", which is why command-line tools use it to signal failure, and why you should leave main returning 0 unless you mean otherwise.

A Worked Example

This program uses nothing beyond what is above: three variables, some arithmetic, and one printf per line of the report.

C
#include <stdio.h>

int main(void) {
    int shifts = 3;
    int crates_per_shift = 18;
    double hours_per_shift = 7.5;

    int total_crates = shifts * crates_per_shift;
    double total_hours = shifts * hours_per_shift;

    printf("Station %s\n", "Bay 4");
    printf("Shifts: %d\n", shifts);
    printf("Crates handled: %d\n", total_crates);
    printf("Hours worked: %.1f\n", total_hours);
    printf("Crates per hour: %.2f\n", total_crates / total_hours);
    return 0;
}
Output
Station Bay 4
Shifts: 3
Crates handled: 54
Hours worked: 22.5
Crates per hour: 2.40

shifts * crates_per_shift multiplies two whole numbers and gives 54. shifts * hours_per_shift mixes a whole number with a decimal one, and C widens the whole number to match, giving 22.5. The last line divides 54 by 22.5, which is 2.4, and %.2f pads it out to two decimal places as 2.40 - printf never drops a digit you asked for.

Notice that "Bay 4" was passed straight to %s without being stored anywhere first. A quoted string is a value like any other; a variable is only needed when you want to use it more than once or give it a name that explains it.

Reading Input

C can read from the keyboard too, with scanf for individual values or fgets for a whole line, and this site gives you an input box next to the editor to type into. None of the examples in these lessons use it, so that every program on the page produces the same output for everybody who runs it. When you are ready to experiment, remember that a program which waits for input it never receives will simply sit there until it times out.

Common Mistakes

Forgetting the \n. The most common cosmetic bug in early C. printf adds nothing you did not ask for, so several calls in a row all land on one line. If your output looks like one long smear, count your newlines.

Putting a semicolon after a preprocessor directive. #include <stdio.h>; is not a statement, and the stray semicolon is left behind as one, which confuses the compiler at the top of your file. No # line ever takes a semicolon.

Mixing up 'a' and "a". Single quotes make one character, a value of type char. Double quotes make a string, which occupies one byte more than the characters you see because of the invisible terminator on the end - a detail the arrays-and-strings lesson goes into properly. They are different types, so %c and %s are not interchangeable.

Expecting a specifier to convert a value. %d does not turn a decimal number into a whole one, and %f does not turn a whole number into a decimal one. A specifier describes what type the value already is; mismatch it and you have told printf to read the bytes it was handed as a type they are not, which is undefined behaviour - anything at all may be printed. Conversions are your job, and the next lesson shows how to ask for one.

Leaving out return 0;. Since C99 this is actually allowed in main specifically, and reaching the closing brace behaves as if you had returned 0. It is still worth writing, because the habit is the same one every other function needs, and there the return really is required.

Assuming the compiler runs your code to find problems. It does not. It checks the shape of your program - are these names declared, do these types fit together, is this syntax legal - and nothing more. A program that divides by zero, or reads past the end of an array, compiles perfectly happily. Catching that class of problem is the subject of most of the rest of this track.

Next Steps

Open the C playground and type the five-line program at the top of this lesson from memory, without copying it. Then break it on purpose, one thing at a time: delete the semicolon after the printf, delete the #include, delete the closing brace. Read each diagnostic and note which line it blames, because they are not always the line you changed.

Then print a small table of your own with three printf calls, using %d for a count, %.2f for a price and %s for a label. The next lesson explains what those three types really are, how much memory each one takes, and the one arithmetic surprise that catches nearly every C beginner.

Write it yourself

Reading about code and writing it are different skills. These exercises practise exactly what this lesson covered; they run in this tab and need no account.