<< Home

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.

💡 When you see historical code in this guide that uses placeholders like {…} 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.

⚠️ C gives you power. It also gives you responsibility. Many runtime errors come from misuse of pointers, lifetimes, or unchecked return values. This guide teaches defensive habits from the start so you can write reliable programs.

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 {…}.

💡 Keep one responsibility per .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:

StageTypical toolWhat it does
Preprocesscpp (built into gcc/clang)Expands #include and macros
Compilegcc, clang, clTurns C into machine code objects
Linkld, link (via compiler driver)Combines objects and libraries to create an executable
ArchivearBundles objects into static libraries
Debuggdb, lldbRuns 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.

💡 Keep a “scratch” directory with tiny single-file programs (for example hello.c, io.c, math.c). You can verify your toolchain and experiment quickly without affecting project code.