thecodex.expert · The Codex Family of Knowledge
Language Reference

C

The closest language to the hardware — no GC, no runtime, just code and memory. The foundation every other language is built on.

Language Reference C17 · C23 (2024) Static typing Last verified:
New to C? This is the reference encyclopedia — dip in anytime. To learn step by step, start the C Course →
Canonical Definition

C is a general-purpose, procedural, statically typed, compiled programming language that compiles to native machine code, provides manual memory management via malloc/free, direct memory access through pointers, and no runtime overhead — the foundational systems programming language of operating systems, kernels, compilers, and embedded systems.

📑 C Reference — All Topics

Pointers & Memory

Stack vs heap, malloc/free, pointer arithmetic, NULL.

Structs & Arrays

Defining structs, arrays, strings as char arrays.

Undefined Behaviour

What it is, the most common causes, how to avoid it.

What is C

The Linux kernel and most of Windows/macOS still run substantially on this.

Setup and Compilers

No official toolchain — GCC and Clang are both free and both production-grade.

Variables and Types

sizeof(int) isn't guaranteed to be 4 — only a MINIMUM size is promised.

Control Flow

C had no boolean type at all until C99 — 0 was false, everything else true.

Functions

No pass-by-reference exists — pass a pointer, or nothing you do sticks.

Arrays and Strings

No string type exists — just a char array that happens to end in a zero byte.

Unions

Every member starts at the SAME address — write one, read a different one, legally.

Dynamic Memory Allocation

Every malloc needs exactly one matching free — miss it, and it's gone forever.

The Preprocessor

A macro without enough parentheses is a classic, real, easy-to-hit bug.

Header Files

#include isn't a real import — it's the preprocessor copy-pasting text.

File IO

fopen returns NULL on failure, not an exception — and nothing forces you to check.

Bitwise Operations

& vs && both compile — and they do genuinely different things.

Function Pointers

qsort doesn't know how to compare YOUR data — you pass it a pointer to a function.

The Standard Library

No networking, no JSON, not even a real string — deliberately, by design.

Multi-file Projects

Each .c file compiles with zero visibility into any other file's internals.

Makefiles

A recipe indented with spaces instead of a real tab produces a cryptic error, every time.

Debugging with GDB

A segfault alone tells you nothing — GDB is how you find the line and the pointer.

Memory Safety Tools

A leak or overflow can compile clean and run silently — until one of these tools finds it.

Linked Lists

A struct holding a pointer to another instance of itself — the root idea behind every C linked structure.

Recursion

C makes no tail-call optimization guarantee — deep recursion can genuinely overflow.

Variadic Functions

printf trusts the format string entirely — a mismatch is undefined behavior, not a caught error.

Enums

A C enum is just a named integer — the compiler won't stop you assigning 9999.

const and volatile

const int *p and int * const p mean completely different things.

Error Handling

No try/catch exists — if you don't check the return value, the error just vanishes.

Concurrency (pthreads)

C11 standardized threads.h — but pthreads is what nearly everyone actually uses.

C Standards History

For a decade, one book WAS the spec — until ANSI finally formalized C in 1989.

One sentence

C is the language that sits closest to the hardware — no garbage collector, no objects, no runtime, just your code, the CPU, and the memory. Every other language was either written in C or heavily influenced by it.

What C is

C is a general-purpose, procedural, statically typed, compiled programming language created by Dennis Ritchie at Bell Labs between 1969 and 1973 to rewrite the Unix operating system. C compiles directly to native machine code — no virtual machine, no interpreter, no garbage collector. The programmer manages memory explicitly with malloc and free. In exchange, C delivers performance as close to hand-written assembly as a high-level language can get, complete control over memory layout, and near-universal portability across every CPU architecture.

C is the language of operating systems (Linux kernel, macOS/iOS XNU kernel, Windows NT), embedded systems, database engines (SQLite, PostgreSQL internals), interpreters (CPython, Ruby MRI), compilers (GCC, Clang), and virtually every piece of systems software that runs the world.

Chello.c
#include <stdio.h>    /* standard I/O — printf, scanf */
#include <stdlib.h>   /* malloc, free, exit */

int main(void) {
    printf("Hello, World!\n");

    /* Variables — types are explicit, no inference */
    int count = 10;
    double price = 29.99;
    char letter = 'A';
    char name[] = "Priya";   /* char array (string) */

    /* printf format codes */
    printf("Name: %s, Count: %d, Price: %.2f\n", name, count, price);

    return 0;   /* 0 = success; non-zero = error */
}

Types and sizes

C's type sizes are platform-defined, not fixed — int is at least 16 bits but usually 32 on 64-bit systems. For fixed-width types, use <stdint.h>: int8_t, int16_t, int32_t, int64_t, and their unsigned variants. size_t is the correct type for memory sizes and array indices — it is pointer-sized (32-bit on 32-bit systems, 64-bit on 64-bit).

Ctypes.c
#include <stdio.h>
#include <stdint.h>   /* fixed-width types */

int main(void) {
    /* Platform-dependent sizes */
    printf("int:    %zu bytes\n", sizeof(int));     /* usually 4 */
    printf("long:   %zu bytes\n", sizeof(long));    /* 4 or 8 */
    printf("double: %zu bytes\n", sizeof(double));  /* 8 */
    printf("char*:  %zu bytes\n", sizeof(char*));   /* 4 or 8 */

    /* Fixed-width — always these sizes */
    int32_t x = 2147483647;    /* exactly 32 bits */
    uint64_t big = 18446744073709551615ULL; /* max uint64 */

    /* size_t — correct type for sizes and indices */
    size_t n = 100;
    int arr[n];   /* VLA — variable length array, C99+ */
    return 0;
}

Pointers — the central concept

A pointer is a variable that holds a memory address. int* p declares a pointer to an int. &x gets the address of x. *p dereferences the pointer — reads or writes the value at the stored address. Pointers are what make C both powerful and dangerous: they enable efficient data structures, direct hardware access, and pass-by-reference — but an invalid pointer access produces undefined behaviour.

Cpointers.c
#include <stdio.h>

int main(void) {
    int x = 42;
    int *p = &x;     /* p holds the address of x */

    printf("x = %d\n", x);     /* 42 */
    printf("p = %p\n", p);     /* address like 0x7fff... */
    printf("*p = %d\n", *p);   /* 42 — dereference: value at p */

    *p = 100;                   /* modify x through the pointer */
    printf("x = %d\n", x);     /* 100 */

    /* Pointer arithmetic */
    int arr[] = {10, 20, 30, 40};
    int *q = arr;               /* q points to arr[0] */
    printf("%d\n", *(q + 2));  /* 30 — arr[2] */

    return 0;
}

Manual memory management

C has no garbage collector. Dynamic memory comes from the heap via malloc (allocate) and must be returned via free (deallocate). Failing to free allocated memory is a memory leak. Freeing memory and then using it is a use-after-free. Freeing the same pointer twice is a double-free. All three are undefined behaviour — they may crash immediately, produce wrong results, or enable security exploits.

Cmemory.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    /* Allocate array on heap */
    int n = 10;
    int *arr = malloc(n * sizeof(int));  /* request n ints */
    if (arr == NULL) {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }

    /* Use the memory */
    for (int i = 0; i < n; i++) arr[i] = i * i;
    printf("%d\n", arr[5]);   /* 25 */

    free(arr);       /* return memory to heap */
    arr = NULL;      /* prevent dangling pointer use */

    /* calloc: allocate + zero-initialise */
    char *buf = calloc(100, sizeof(char));
    strncpy(buf, "Hello", 99);
    printf("%s\n", buf);
    free(buf);

    return 0;
}

Strings in C

C has no built-in string type. A C string is a null-terminated array of char: the sequence ends with a '\0' (NUL) byte. strlen(s) counts bytes up to (not including) the NUL. strcpy, strcat, sprintf: all dangerous if the destination buffer is too small. Use strncpy, strncat, snprintf with explicit size limits. Buffer overflows — writing past the end of a string buffer — are the most common class of C security vulnerability.

Structs, unions, and enums

C organises data with structs (named groups of fields), unions (multiple fields sharing the same memory — only one valid at a time), and enums (named integer constants). Structs are C's only mechanism for grouping related data — there are no classes. Functions operating on structs are defined separately and receive a pointer to the struct as a parameter, forming the basis of C's OOP-like patterns used in the Linux kernel and other large C codebases.

Cstructs.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char name[64];
    double balance;
} BankAccount;

/* "Method" — takes pointer to struct */
void deposit(BankAccount *acc, double amount) {
    if (amount > 0) acc->balance += amount;
}

void print_account(const BankAccount *acc) {
    printf("Account(%s: %.2f)\n", acc->name, acc->balance);
}

int main(void) {
    BankAccount acc;
    strncpy(acc.name, "Priya", sizeof(acc.name) - 1);
    acc.balance = 1000.0;

    deposit(&acc, 500.0);
    print_account(&acc);   /* Account(Priya: 1500.00) */

    /* Heap-allocated struct */
    BankAccount *heap_acc = malloc(sizeof(BankAccount));
    strncpy(heap_acc->name, "Rahul", sizeof(heap_acc->name) - 1);
    heap_acc->balance = 2000.0;
    free(heap_acc);

    return 0;
}

Function pointers and callbacks

Functions are not first-class values in C, but function pointers enable passing functions as arguments — a form of callbacks and polymorphism. The standard library's qsort and bsearch accept a comparison function pointer. Many event-driven C systems (libuv, GTK, callbacks in embedded firmware) use function pointers as their core abstraction.

Cfunction_pointers.c
#include <stdio.h>
#include <stdlib.h>

int compare_ints(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);  /* comparison for qsort */
}

int main(void) {
    int arr[] = {5, 2, 8, 1, 9, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    /* qsort takes a function pointer */
    qsort(arr, n, sizeof(int), compare_ints);

    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    /* 1 2 3 5 8 9 */

    /* Declare a function pointer variable */
    int (*cmp)(const void*, const void*) = compare_ints;
    printf("\ncmp ptr: %p\n", (void*)cmp);

    return 0;
}

The preprocessor

The C preprocessor runs before compilation, performing text substitution. #include pastes file contents. #define defines macros — text replacements before parsing. #ifdef/#ifndef/#endif conditionally include code. Macros are powerful but dangerous — they don't respect scope or types. Prefer const variables over #define for constants and inline functions over function-like macros.

Cpreprocessor.c
#include <stdio.h>

#define MAX_SIZE 100           /* constant macro — prefer const int */
#define MIN(a,b) ((a)<(b)?(a):(b))  /* function macro — has pitfalls */
/* MIN(x++, y++) — x or y incremented TWICE! Use inline function instead */

/* Header guard — prevent double-inclusion */
#ifndef MYHEADER_H
#define MYHEADER_H

typedef struct { int x, y; } Point;
int distance(Point a, Point b);

#endif  /* MYHEADER_H */

/* Conditional compilation */
#ifdef DEBUG
    #define LOG(msg) fprintf(stderr, "[DEBUG] %s\n", msg)
#else
    #define LOG(msg) ((void)0)   /* no-op in release */
#endif

Undefined behaviour: the compiler's contract

ISO C defines many operations as undefined behaviour (UB): signed integer overflow, null pointer dereference, out-of-bounds array access, use-after-free, reading uninitialised variables, data races. When UB occurs, the C standard gives compilers permission to generate any code. Compilers assume UB never occurs and optimise accordingly — meaning UB can cause code to be silently deleted, produce security holes, or behave correctly during testing but fail in production. Use -fsanitize=address,undefined (AddressSanitizer + UBSan) to detect UB at runtime during development.

Commonly confused
C and C++ are different languages. C++ is a separate language that started as "C with Classes" but has evolved far beyond C. C++ adds templates, exceptions, RAII, the STL, lambdas, and much more. Valid C is not always valid C++ — they share syntax but have diverged significantly. The Linux kernel, for example, is written strictly in C, not C++.
Arrays in C are not objects — they decay to pointers. int arr[10]: passing arr to a function passes a pointer to the first element. sizeof(arr) inside the declaring function gives the total bytes; sizeof(arr) inside any other function gives the pointer size (8 bytes on 64-bit). Always pass the array length as a separate parameter.
Undefined behaviour is not just "anything might happen" in theory. In practice, compilers actively use UB to enable optimisations — and those optimisations can turn a minor bug into a security vulnerability. A signed integer overflow check that the compiler determines is checking for UB may be silently deleted. This is not theoretical: CVEs have been caused by exactly this.
How this connects
Enables

The C abstract machine and the as-if rule

ISO C defines a program's behaviour in terms of an abstract machine — a hypothetical computer with a specific memory model, sequence points, and execution rules. The compiler is free to generate any code as long as the observable behaviour (I/O, volatile accesses, and the program's return value) matches what the abstract machine would produce. This is the as-if rule (ISO C17 §5.1.2.3). The as-if rule allows compilers to reorder, eliminate, and transform operations freely — constant folding, dead code elimination, loop unrolling — as long as the observable output is identical. When UB is present, the abstract machine's behaviour is undefined, so the as-if rule applies to nothing — compilers may produce literally anything.

Memory models: stack, heap, BSS, text, data

A C process's virtual memory is divided into segments: text — executable code (read-only, shared between processes running the same binary); data — initialised global/static variables (int x = 5; at file scope); BSS (Block Started by Symbol) — uninitialised global/static variables, zero-initialised by the OS loader; stack — local variables, function call frames, grows downward; heap — dynamic allocations via malloc, grows upward. Stack allocation is O(1) — just decrement the stack pointer. Heap allocation is managed by the allocator (jemalloc, tcmalloc, glibc malloc) — typically O(1) amortised but with fragmentation overhead.

C17, C23 and the standards timeline

C89/C90: the original ANSI/ISO standard. Declarations must precede statements; no bool, no inline. C99: added inline, mixed declarations/code, variable-length arrays (VLAs), _Bool, stdint.h, stdbool.h, designated initialisers, compound literals. C11: _Generic type-generic expressions, _Static_assert, anonymous structs/unions, thread support (threads.h), atomic operations (stdatomic.h). C17: defect fixes only — no new features. C23 (published 2024): nullptr keyword, binary literals 0b1010, #embed for embedding binary data, typeof, constexpr, improved attributes.

Specification reference

ISO/IEC 9899:2018 — C17 standard. iso.org/standard/74528.html. Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall — K&R. The reference implementation of C for most purposes. Open standard draft: open-std.org/JTC1/SC22/WG14/.

Sources

1
ISO/IEC 9899:2018. C17 — Programming languages — C. International Organization for Standardization. iso.org/standard/74528.html.
2
Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall. — The canonical C reference.
3
Seacord, R. C. (2013). Secure Coding in C and C++ (2nd ed.). Addison-Wesley. — Undefined behaviour, buffer overflows, and secure C patterns.
4
Open Standards. C23 working draft N3220. open-std.org/JTC1/SC22/WG14/. — C23 standard draft.
5
LLVM Project. Clang documentation: AddressSanitizer, UndefinedBehaviorSanitizer. clang.llvm.org/docs/.
Source confidence: High Last verified: Primary source: ISO C17 (9899:2018) · K&R The C Programming Language