A language tour
No abstractions you didn't ask for. No runtime you didn't choose. Just you, the machine, and a pointer.
01 — Close to the Metal
C was written in 1972 to write Unix. It's not a language for ease — it's a language for control. When you write C, you know where every byte lives and who owns it.
"C is quirky, flawed, and an enormous success. Although accidents of history surely helped, it evidently satisfied a need for a system implementation language efficient enough to displace assembly language, yet sufficiently abstract and fluent to describe algorithms and interactions in a wide variety of environments."
— Dennis Ritchie, creator of C/* The program that introduced a generation to C */ #include <stdio.h> int main(void) { printf("hello, world\n"); return 0; } /* Every C program starts here. main() is not magic — it's just a convention the linker and OS agree on. */
The original "hello, world" from Kernighan & Ritchie's 1978 book set the template for virtually every programming tutorial that followed.
02 — Pointers
Pointers are C's defining feature and its sharpest edge. They let you address memory directly — build any data structure, pass by reference, and understand what every other language hides from you.
#include <stdio.h> void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; } int main(void) { int x = 10, y = 20; printf("Before: x=%d, y=%d\n", x, y); swap(&x, &y); /* pass addresses, not values */ printf("After: x=%d, y=%d\n", x, y); /* A pointer is just a number — an address */ int *p = &x; printf("Address of x: %p\n", (void*)p); printf("Value at that address: %d\n", *p); return 0; }
&x means "give me the address of x." *p means "give me whatever is at that address." These two operators unlock the entire machine.
03 — Manual Memory Management
C has no garbage collector. You ask for memory with malloc, you use it, and you give it back with free. The discipline this demands teaches you what every managed language is silently doing on your behalf.
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char name[64]; int score; } Player; int main(void) { /* Allocate an array of 3 players on the heap */ Player *team = malloc(3 * sizeof(Player)); if (!team) { perror("malloc"); return 1; } strncpy(team[0].name, "Alice", 63); team[0].score = 95; strncpy(team[1].name, "Bob", 63); team[1].score = 87; strncpy(team[2].name, "Carol", 63); team[2].score = 92; for (int i = 0; i < 3; i++) printf("%s: %d\n", team[i].name, team[i].score); free(team); /* your responsibility, every time */ return 0; }
Always check if malloc returned NULL. Always call free. These two habits separate careful C from code that leaks.
04 — Structs & Composition
C has no classes, no inheritance, no method dispatch. What it has is the struct — a way to pack data together and name it. Combined with function pointers, structs can express surprisingly rich design.
typedef struct Vec2 { float x, y; } Vec2; /* "Methods" are just functions that take a pointer */ Vec2 vec2_add(Vec2 a, Vec2 b) { return (Vec2){ a.x + b.x, a.y + b.y }; } float vec2_length(Vec2 v) { return sqrtf(v.x * v.x + v.y * v.y); } int main(void) { Vec2 a = { 3.0f, 0.0f }; Vec2 b = { 0.0f, 4.0f }; Vec2 c = vec2_add(a, b); printf("length = %.1f\n", vec2_length(c)); /* 5.0 */ return 0; }
The compound literal (Vec2){ 3.0f, 0.0f } creates a temporary struct inline — a modern C99 idiom that reads cleanly without a named variable.
05 — The Preprocessor
Before the compiler sees your C, the preprocessor runs — expanding macros, including headers, conditionally compiling blocks. It's a blunt instrument, but it's also why the same C code runs on a microcontroller and a supercomputer.
#include <stdio.h> #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define ARRAY_LEN(arr) (sizeof(arr) / sizeof((arr)[0])) #ifdef DEBUG #define LOG(fmt, ...) fprintf(stderr, "[DEBUG] " fmt "\n", ##__VA_ARGS__) #else #define LOG(fmt, ...) /* nothing in release */ #endif int main(void) { int scores[] = { 42, 17, 99, 53 }; int n = ARRAY_LEN(scores); LOG("Processing %d scores", n); int best = scores[0]; for (int i = 1; i < n; i++) best = MAX(best, scores[i]); printf("Best: %d\n", best); /* 99 */ return 0; }
ARRAY_LEN works because sizeof is evaluated at compile time. This is a classic C idiom — no runtime overhead, no magic, just the compiler doing arithmetic.
06 — The Whole Picture
Unix, Linux, Python, Ruby, Lua — most of the software stack you use daily is written in or built with C.
C compiles to near-optimal machine code. No VM, no JIT warmup, no runtime tax — just instructions.
The same C source runs on a 8-bit microcontroller and a 64-core server. No other language matches this range.
C89, C99, C11, C17, C23 — decades of careful, deliberate standardisation. Slow-moving is a feature.
Every language has a C FFI. C is the lingua franca of inter-language communication.
Learning C teaches you what memory is, what a stack frame is, and why cache misses hurt. Priceless context.