Introduction to C

C was written in the early 1970s at Bell Labs, mostly by Dennis Ritchie, to build Unix. Fifty-plus years later it's still the language most operating system kernels, device drivers, and language runtimes are written in. Learning it means learning how a computer actually executes your instructions, not just how to get a result.

Why C looks the way it does

Higher-level languages like Python or JavaScript manage memory for you, figure out types on the fly, and run through an interpreter or virtual machine. C does none of that. It compiles straight down to the machine instructions your processor runs, gives you direct access to memory addresses, and expects you to declare exactly what kind of data you're working with before you use it. That's not C being old-fashioned — it's C giving you control that other languages deliberately take away in exchange for convenience. Once you understand why C works this way, concepts in other languages — garbage collection, references, buffer overflows as a security topic — stop being abstract.

Compiling instead of running

A Python file runs the moment you type python script.py. A C file doesn't run at all until it's been turned into an executable — a file full of actual machine instructions for your specific processor and operating system. The tool that does this translation is a compiler; on most systems that's gcc (the GNU Compiler Collection) or clang.

Here's the smallest complete C program:

</> hello.c
#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

To turn that into a program you can actually run, you compile it, then execute the result:

</> terminal
gcc hello.c -o hello
./hello
Output
Hello, world!

The -o hello tells gcc what to name the resulting executable (otherwise it defaults to a file called a.out). ./hello then runs that file. If you skip compiling and just try to run hello.c directly, your shell will complain — a .c file is source code, not something your operating system knows how to execute on its own.

Reading the pieces of that program

  • #include <stdio.h> pulls in declarations for the standard input/output library — it's how the compiler knows what printf is and what it expects.
  • int main(void) is the function every C program starts running from. The int means main hands back a whole number when it finishes; void means it takes no arguments.
  • printf(...) writes text to the terminal. The \n inside the string is a newline character — without it, whatever runs next would print right after "world!" on the same line.
  • return 0; is main handing that number back — by convention, 0 tells the operating system the program finished successfully. Nonzero values signal that something went wrong, which shell scripts and other programs can check for.

Every statement ends with a semicolon, and blocks of code are wrapped in curly braces. Neither is optional — the compiler uses them to know where one instruction ends and the next begins, and it will refuse to compile if you leave one out.

A second example

You can call printf as many times as you like in a single program:

</> facts.c
#include <stdio.h>

int main(void) {
    // Print two separate lines
    printf("C was created in 1972.\n");
    printf("It still runs your operating system today.\n");
    return 0;
}
Output
C was created in 1972.
It still runs your operating system today.
Note: anything after // on a line is a comment — the compiler ignores it entirely. Comments are for you and anyone else reading the code later, not for the machine.