ZetCode

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.

minimal.c
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
/*
 * 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.

statistics.c
#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.

printf_format.c
#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.

escapes.c
#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.

age.c
#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.

status.c
#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

My name is Jan Bodnar, and I'm a dedicated programmer with a deep passion for coding. Since 2007, I've been sharing my expertise through over 1,400 articles and 8 e-books. With more than a decade of teaching experience, I strive to make programming accessible and engaging.

List all C tutorials.