Chapter 1: Introduction to C
C is a small, efficient, and portable systems language. It grew alongside UNIX and went on to shape how we think about programs, memory, and operating systems. This chapter sets the scene, explains why C still matters, and gets you to your first working program.
The origins of C
C’s roots trace to Cambridge in the 1960s with BCPL (Basic Combined Programming Language). BCPL influenced B at Bell Labs. Dennis Ritchie then evolved B into C while he and colleagues developed UNIX. The language and the operating system informed each other; C provided performance and low-level control; UNIX provided a portable systems context that rewarded a simple, consistent language.
BCPL, B, and the C lineage
BCPL used one word-sized type and emphasized portability via an intermediate representation. B carried forward that simplicity. C introduced a richer type system, pointer arithmetic, and a standard library. Together with UNIX, this created a portable toolchain that spread to diverse hardware.
A language designed for systems
C was designed to write operating systems and tools. That goal shaped the language: direct memory access with pointers; well-defined operators; a minimal runtime; and compilation to fast native code. The result is a language close to the machine yet high-level enough for portable software.
{…} or comments such as /* … */, read them as “content omitted here” (not as syntax you need to type verbatim).
Why C endures
C remains relevant because it balances small surface area with expressiveness. The core language is compact; the standard library is focused; compilers are mature and fast. That combination makes C suitable for kernels, drivers, embedded systems, high-performance libraries, and language runtimes.
A compact core you can master
You can learn the syntax and main patterns of C quickly. Mastery then comes from understanding memory, data layout, and the build process. This guide follows that path: language first; memory and pointers next; then files, networking, and cross-platform builds.
Portability by design
Well-written C compiles on Linux, macOS, and Windows with only minor conditional code. The standard library covers essential tasks (I/O, memory, strings, time, math) while leaving platform specifics to opt-in headers.
The structure of a C program
Every C program consists of declarations and definitions, one of which must be a function named main. You include headers with #include to use the standard library. You compile translation units (.c files) and link them into an executable.
A minimal skeleton
Here is a minimal skeleton for a basic C program:
/* file: hello.c */
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
Header directives begin with # and are handled by the preprocessor. The function main returns an integer to the host environment (zero on success). Braces mark blocks. Statements end with semicolons.
Declarations, definitions, and headers
A declaration tells the compiler about a name and its type. A definition allocates storage or provides function code. You put reusable declarations in .h headers and implementations in .c files. For example: int add(int a, int b); declares a function; its definition provides the body with {…}.
.c file; export only what others need in the paired .h. This structure scales from single-file examples to real projects.
Compilers and toolchains
The compiler turns source into machine code; the linker combines compiled objects into an executable; tools like archivers and debuggers complete the toolchain. On Unix-like systems you will most often use gcc or clang. On Windows you can use cl from MSVC; you can also use GCC or Clang via MinGW or WSL.
Common toolchain roles
Here is a list of common toolchain roles:
| Stage | Typical tool | What it does |
| Preprocess | cpp (built into gcc/clang) | Expands #include and macros |
| Compile | gcc, clang, cl | Turns C into machine code objects |
| Link | ld, link (via compiler driver) | Combines objects and libraries to create an executable |
| Archive | ar | Bundles objects into static libraries |
| Debug | gdb, lldb | Runs and inspects your program with symbols |
Compile commands
Here are compile commands for different systems:
# GCC (Linux, macOS, MinGW)
gcc -std=c23 -Wall -Wextra -O2 hello.c -o hello
# Clang (Linux, macOS)
clang -std=c23 -Wall -Wextra -O2 hello.c -o hello
# MSVC (Developer Command Prompt on Windows)
cl /std:c23 /W4 /O2 hello.c
Use warnings aggressively (-Wall -Wextra or /W4). Specify a standard level (-std=c23 or /std:c23) so builds are predictable.
A first “Hello, world” program
Let’s compile and run the minimal program from earlier. Save it as hello.c, then run one of the commands below for your platform.
Using Linux and macOS with GCC or Clang
Use this with Linux and macOS:
gcc -std=c23 -Wall -Wextra hello.c -o hello
./hello
If you prefer Clang, replace gcc with clang. The output should be a single line saying Hello, world!.
Using Windows with MSVC or MinGW
On Windows use this:
:: MSVC (in a Developer Command Prompt)
cl /std:c23 /W4 hello.c
hello.exe
:: MinGW (GCC on Windows)
gcc -std=c23 -Wall -Wextra hello.c -o hello.exe
hello.exe
If the compiler cannot find headers such as <stdio.h>, check your installation path or use a shell environment that provides the toolchain.
hello.c, io.c, math.c). You can verify your toolchain and experiment quickly without affecting project code.