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.
- C89, also called C90 or ANSI C, was published in 1989 and ratified by ISO in 1990. It is the base that all later revisions extend.
- C95 added a few wide-character facilities and is rarely mentioned on its own.
- C99 added declarations in the middle of a block,
//comments, thelong longtype, variable-length arrays, designated initializers and the<stdint.h>header. - C11 added threads, anonymous structures and unions,
static assertions, and the
_Genericselection. - C17 fixed defects and clarified wording; it introduced no new features.
- C23 added
typeof,constexpr,nullptr,#embed, attributes, and madebool,trueandfalsekeywords.
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.
#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.
#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.
#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.
#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.
#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.
#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
List all C tutorials.