C Language Essentials

The language of systems programming. Direct memory access, no garbage collector, compiles to native machine code. Almost every OS kernel, database engine, and embedded system is written in C.

Why It Matters

C is the lingua franca between hardware and software. Understanding C means understanding how computers actually work — memory layout, pointer arithmetic, calling conventions. Even if you primarily write Python or Go, knowing C makes you a better systems thinker.

Compilation Pipeline

source.c → Preprocessor → Compiler → Assembler → Linker → executable
              (#include,      (C → asm)   (asm → .o)   (.o → binary)
               #define)
gcc -E main.c           # stop after preprocessing (expand macros/includes)
gcc -S main.c           # stop after compilation (produces main.s assembly)
gcc -c main.c           # stop after assembly (produces main.o object file)
gcc main.o utils.o -o program  # link object files into executable

Core Language Features

Pointers

int x = 42;
int *p = &x;    // p holds the ADDRESS of x
*p = 99;        // dereference: x is now 99
 
// Function pointers — callbacks, dispatch tables
int add(int a, int b) { return a + b; }
int (*op)(int, int) = add;
printf("%d\n", op(3, 4));  // 7

Structs and Memory Layout

typedef struct {
    char name[64];  // offset 0
    int age;        // offset 64 (possibly padded to 68)
    float score;    // offset 68
} Student;          // total: 72 bytes (with padding)
 
Student s = {"Alice", 22, 95.5};
printf("%s is %d\n", s.name, s.age);
 
// Struct padding — compiler aligns fields to their natural boundary
// Use sizeof() to check, never assume sizes
printf("size: %zu\n", sizeof(Student));

Manual Memory Management

int *arr = malloc(100 * sizeof(int));
if (!arr) { perror("malloc"); exit(1); }
arr[0] = 42;
free(arr);
arr = NULL;  // prevent dangling pointer

No garbage collector. Every malloc needs a free. Forgetting = memory leak. Double-freeing = crash or corruption.

Const and Volatile

const int *p;        // pointer to const int — can't modify *p
int *const p;        // const pointer to int — can't modify p itself
const int *const p;  // both const
 
volatile int *reg;   // tells compiler: don't optimize away reads
                     // essential for memory-mapped IO and signal handlers

Undefined Behavior

C trusts the programmer. Violations don’t always crash — they silently corrupt:

UB ExampleWhat Can Happen
Array out of boundsOverwrites adjacent memory
Signed integer overflowCompiler may optimize away checks
Dereferencing NULLSegfault (or worse, silent corruption)
Using uninitialized variableRandom garbage value
Modifying string literalSegfault (literals are in read-only memory)

The compiler is allowed to assume UB never happens. This means if (x + 1 < x) (signed overflow check) may be optimized away entirely.

Compilation Flags You Should Always Use

gcc -Wall -Wextra -O2 -g -o program main.c
# -Wall -Wextra: enable warnings (catches most bugs at compile time)
# -fsanitize=address,undefined: runtime detection of memory/UB bugs
# -g: debug symbols for GDB