C basics
last modified November 14, 2026
Every C program is built from the same few pieces: preprocessor directives, comments, declarations, statements and functions. This tutorial walks through them in the order a beginner meets them, using complete programs rather than fragments, and explains what the compiler does with each part.
The smallest program
A C program can be as short as one function. The next file contains no declarations, no library calls and no output, but it is a complete program that compiles and runs.
int main(void) {
return 0;
}
main is special: the C runtime calls it after the program starts,
and the process ends when it returns. The return type must be int,
and the returned value is the exit status. void in the parameter
list states that the function takes no arguments; an empty pair of parentheses
would also compile but leaves the parameters unspecified.
$ gcc -std=c17 -Wall -Wextra -o minimal minimal.c $ ./minimal $ echo $? 0
Program structure
A normal program adds a preprocessor directive, comments and calls into the standard library. The next example prints a small table and annotates every part.
/*
* structure.c - print a small table.
*/
#include <stdio.h>
int main(void) {
/* printf writes to standard output */
printf("name value\n");
printf("----- -----\n");
printf("alpha 1\n"); // line comment
return 0; /* success */
}
The file has four kinds of lines: a block comment, the #include
directive, the definition of main, and statements inside it. Line
comments with // were added in C99 and run to the end of the line;
block comments with /* */ may span several lines but do not nest.
Neither kind survives preprocessing, so comments cost nothing at run time.
$ gcc -std=c17 -Wall -Wextra -o structure structure.c $ ./structure name value ----- ----- alpha 1
Declarations and statements
A declaration introduces a name and its type and may also give an initial value. A statement is an instruction that is executed: an assignment, a function call, a loop or a return. In C89 every declaration had to come before the first statement of a block; since C99 declarations may appear anywhere, as in the next program.
#include <stdio.h>
int main(void) {
int total = 0;
total = total + 5;
int next = total * 2; /* a declaration after a statement */
printf("total: %d\n", total);
printf("next: %d\n", next);
return 0;
}
next is declared and initialized after an assignment, which is
legal in C99 and later. Its initializer is evaluated at that point, so it sees
the value that total has at that moment, namely 5. A declaration
without an initializer would leave the variable with an indeterminate value.
$ gcc -std=c17 -Wall -Wextra -o statistics statistics.c $ ./statistics total: 5 next: 10
Printing values
printf takes a format string and the values to insert into it. Each
conversion specification starts with % and names the type of the
matching argument, so the order and the types must agree.
#include <stdio.h>
int main(void) {
int count = 12;
double price = 2.5;
char grade = 'A';
const char *name = "widget";
printf("count: %d\n", count);
printf("price: %.2f\n", price);
printf("grade: %c\n", grade);
printf("name: %s\n", name);
printf("hex: %x\n", count);
printf("padded: %5d|\n", count);
printf("size: %zu\n", sizeof count);
printf("percent: 100%%\n");
return 0;
}
%d prints an int, %f a
double, %c a single character and %s a
string that ends at the first null byte. A width such as %5d pads
the field to five characters, and a precision such as %.2f fixes
the number of digits after the decimal point. %zu is the correct
specification for size_t, the type that sizeof
returns, and %% prints a literal percent sign.
$ gcc -std=c17 -Wall -Wextra -o printf_format printf_format.c $ ./printf_format count: 12 price: 2.50 grade: A name: widget hex: c padded: 12| size: 4 percent: 100%
Escape sequences
A string literal may contain escape sequences, which start with a backslash and stand for characters that cannot be typed directly or would be ambiguous. The next program prints a tab, quotation marks, a backslash and two letters written as numeric escapes.
#include <stdio.h>
int main(void) {
printf("tab:\tbetween\n");
printf("quote: \"x\"\n");
printf("backslash: \\\n");
printf("hex escape: \x41\x42\n");
printf("octal escape: \101\102\n");
return 0;
}
\t is a horizontal tab, \n a newline,
\\ a single backslash and \" a quotation mark inside a
string. A hexadecimal escape takes any number of hex digits, while an octal
escape takes up to three; both \x41 and \101 are the
letter A. An unknown escape such as \q is not portable, and GCC
warns about it with -Wall.
$ gcc -std=c17 -Wall -Wextra -o escapes escapes.c $ ./escapes tab: between quote: "x" backslash: \ hex escape: AB octal escape: AB
Reading input
scanf is the input counterpart of printf. It reads
characters from standard input, converts them according to the format string
and stores the results through pointers, which is why the address operator
& is needed for an ordinary variable.
#include <stdio.h>
int main(void) {
int age;
printf("Enter your age:\n");
if (scanf("%d", &age) != 1) {
fprintf(stderr, "not a number\n");
return 1;
}
printf("next year you will be %d\n", age + 1);
return 0;
}
scanf returns the number of items it converted successfully, so
comparing the result with 1 detects input that is not a number. When the check
fails, the program writes an error to standard error with fprintf
and returns 1. The prompt ends with a newline, so the answer starts on its own
line and the output stays readable when the input is piped from a file.
$ gcc -std=c17 -Wall -Wextra -o age age.c $ printf '30\n' | ./age Enter your age: next year you will be 31 $ printf 'thirty\n' | ./age not a number Enter your age:
Return values and exit status
The value that main returns becomes the exit status of the process.
The shell stores it in $?, and the operators &&
and || test it directly: the command after &&
runs only after success, the command after || only after failure.
#include <stdio.h>
int main(void) {
printf("checking configuration\n");
return 1;
}
Zero is the conventional success status, and any other value reports failure.
The same program therefore behaves differently under the two operators: the
&& branch is skipped, while the || branch runs.
Scripts and build systems rely on this convention instead of parsing output.
$ gcc -std=c17 -Wall -Wextra -o status status.c $ ./status; echo "exit status: $?" checking configuration exit status: 1 $ ./status || echo "reported a failure" checking configuration reported a failure
Source
cppreference: basic concepts
cppreference: printf
cppreference: scanf
cppreference: escape sequences
This tutorial covered the elements of a C program: the smallest possible
main, the structure of a file with directives and comments,
declarations mixed with statements, formatted output, escape sequences, input
with scanf, and exit status. The types behind the conversions are
described in C data types, and the loop and
branch statements in C if else and
C while.
Author
List all C tutorials.