ZetCode

C introduction

last modified November 14, 2026

C is a small, statically typed, compiled language that has been in continuous use since the 1970s. Operating system kernels, language runtimes, database engines and embedded firmware are still written in it, and most other languages implement their core in C or in something derived from it. This tutorial explains what C is, which versions of the language exist, how a C program is compiled and run, and what the parts of the smallest program mean.

What C is

C was created by Dennis Ritchie at Bell Labs in 1972, together with the Unix operating system that it was written to implement. It descends from ALGOL and from the typeless language B, and it was designed to be close to the machine while still readable: the language itself has no input, output, strings or memory management, and all of those come from a standard library of functions. A C program is therefore a collection of functions that call each other and the library, and the compiler translates them into machine instructions ahead of time.

The language is statically typed, which means that every object has a type known while the program is compiled, and the compiler can check that operations are applied to compatible values. It is also weakly checked at run time: an array index or a pointer is not validated when the program runs, so mistakes of that kind produce undefined behavior instead of an exception. This combination gives C its speed and its reputation for being unforgiving.

The ISO standards

C is standardized by ISO/IEC JTC1/SC22/WG14, and every revision adds features while keeping almost all existing programs valid. The name of a revision is usually written with the last two digits of its year.

The compiler chooses a revision with the -std option, for example -std=c17 or -std=c2x. GCC 13 defaults to -std=gnu17, which is C17 plus GNU extensions, so an explicit option is the only way to know exactly which language a program is compiled as.

The first program

The traditional first program prints one line. It needs no input, no computation and no data structures, so everything in it is part of the minimum that a C program must contain.

hello.c
#include <stdio.h>

int main(void) {

    printf("Hello, world!\n");

    return 0;
}

#include <stdio.h> is a preprocessor directive: before compilation begins, the contents of the standard input/output header are inserted at that point, which is what declares printf. The function main is the entry point that the C runtime calls, and it must return int. The empty parentheses after void mean that it takes no arguments. printf writes formatted text to standard output, and \n inside the string is the newline character.

The source file is translated by the compiler driver gcc, which runs the preprocessor, the compiler proper, the assembler and the linker in turn. The -o option names the executable; without it the driver would call the result a.out.

$ gcc -std=c17 -Wall -Wextra -o hello hello.c
$ ./hello
Hello, world!

How the compiler turns source into a program

The translation happens in four stages, and each can be observed separately. The preprocessor expands macros, includes headers and removes comments, and produces a translation unit of pure C. The compiler proper turns it into assembly language, the assembler into an object file with the .o suffix, and the linker combines object files with the standard library to produce the executable. The options -E, -S and -c stop after the first, second and third stage. The next program contains a macro, so the result of the first stage shows exactly what the compiler proper receives.

answer.c
#define ANSWER 42

int main(void) {

    return ANSWER;
}

The -E option stops after preprocessing, and -P suppresses the line markers that record where each line came from. The macro has been replaced by its value and the rest of the file is unchanged. Without -P the output would also contain hundreds of lines from the headers of a real program.

$ gcc -E -P answer.c
int main(void) {
    return 42;
}

Checking which standard is active

The preprocessor macro __STDC_VERSION__ expands to the revision of the language that the compiler is using, in the form yyyymm as a long constant. It does not exist in C89, where the language had no such macro.

version.c
#include <stdio.h>

int main(void) {

    printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);

    return 0;
}

The same source compiled with four different options reports four different values. C99 is 199901, C11 is 201112 and C17 is 201710. GCC 13 reports 202000 for -std=c2x, an interim value; the published C23 standard uses 202311.

$ gcc -std=c99 -o version99 version.c
$ ./version99
__STDC_VERSION__ = 199901
$ gcc -std=c11 -o version11 version.c
$ ./version11
__STDC_VERSION__ = 201112
$ gcc -std=c17 -o version17 version.c
$ ./version17
__STDC_VERSION__ = 201710
$ gcc -std=c2x -o version2x version.c
$ ./version2x
__STDC_VERSION__ = 202000

A program with a function

A real program is divided into functions. The next example defines square below main and declares it above, so that main can call it. The declaration is called a prototype and gives the compiler the parameter and return types it needs to check the call.

two_functions.c
#include <stdio.h>

int square(int n);

int main(void) {

    int v = 7;

    printf("%d squared is %d\n", v, square(v));

    return 0;
}

int square(int n) {

    return n * n;
}

The argument v is copied into the parameter n when square is called, and the returned value is passed to printf as its third argument. C passes all arguments by value, which is why changing n inside the function would not affect v.

$ gcc -std=c17 -Wall -Wextra -o two_functions two_functions.c
$ ./two_functions
7 squared is 49

The exit status

The value returned by main becomes the exit status of the process, which the shell reads as $?. Zero means success; any other value means failure, and the meaning of particular non-zero values is up to the program.

exit_status.c
#include <stdio.h>

int main(void) {

    printf("about to exit with status 3\n");

    return 3;
}

The program prints its message and returns 3, and the shell reports that value in $?. Scripts use the same mechanism with && and || to run a command only after success or only after failure.

$ gcc -std=c17 -Wall -Wextra -o exit_status exit_status.c
$ ./exit_status
about to exit with status 3
$ echo $?
3

Compiler warnings

The options -Wall and -Wextra enable a large set of diagnostics that are not errors but usually indicate a mistake. The next program compiles successfully and still produces a warning, because unused is never read.

warnings.c
#include <stdio.h>

int main(void) {

    int unused = 42;

    printf("compiled\n");

    return 0;
}

The compiler prints the file, the line, the column and the name of the warning option, and the build still succeeds with exit status 0. Warnings should be treated as errors in new code: -Werror turns them into failures, and -pedantic reports uses of extensions that a strict reading of the standard does not allow.

$ gcc -std=c17 -Wall -Wextra -o warnings warnings.c
warnings.c: In function ‘main’:
warnings.c:5:9: warning: unused variable ‘unused’ [-Wunused-variable]
    5 |     int unused = 42;
      |         ^~~~~~

Compiling with Clang

Clang accepts the same options and produces a program with the same behavior, which is why the examples in this tutorial work with both compilers. Only the wording of diagnostics differs: Clang writes [-Wunused-variable] in the same place but phrases the message differently, so build scripts should match on the option name rather than on the text.

Source

cppreference: C language
cppreference: macro replacement
GCC: invoking GCC
cppreference: C compiler support

This tutorial covered an introduction to C: its origin and design, the ISO revisions from C89 to C23, the first program and its parts, the four stages of translation, the exit status and compiler warnings. The syntax of a complete program is described in C basics; the types a program can use are covered in C data types, and the options that control the compiler in GCC and Clang.

This tutorial introduced C: its origin and design, the ISO revisions from C89 to C23, the first program and its parts, the four stages of translation, the exit status and compiler warnings. The syntax of a complete program is described in C basics; the types a program can use are covered in C data types, and the options that control the compiler in GCC and Clang.

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.