Video summary

How I program C

Main summary

Key takeaways

Technology

Summary (Advanced C programming: programming style, tools, and low-level concepts)

The speaker argues for an “advanced but controlled” approach to C programming: embrace C’s explicitness and complexity (rather than avoiding it), build long-lasting systems, and rely on compiler help, strict clarity, and custom tooling for debugging and documentation. The talk is presented as a high-level guide to how the speaker programs, with examples spanning memory management, API design, naming conventions, and performance/correctness tradeoffs.


Core philosophy & approach

  • Results → control: Start simple, but eventually you want control over complexity. Developers often begin by seeking convenience (e.g., simple HTML), then later realize deeper mechanisms (tags/CSS/features) are necessary.
  • Learn “how it’s made”: Programming well requires wanting to understand the underlying complexity—like asking what tools/ingredients a kitchen provides, not just ordering a finished omelet.
  • C’s longevity: C persists because many newer language designs carry assumptions that C avoids; C’s “problem” isn’t losing, it’s being optimized for different priorities—control and explicitness.

Memory management: control beats convenience

Garbage collection vs manual memory control

  • The speaker critiques garbage collection:
    • It reduces manual work early.
    • Later it can harm performance and remove timing/control (e.g., freeing memory when it’s optimal).
  • Manual memory problems are framed as solvable with the right tooling and programmer control.

Custom memory debugging with macros

  • Uses __FILE__ / __LINE__ (via macros) to build a memory tracker:
    • Wrap allocation/free to record where memory was allocated and freed.
    • Print allocations and detect memory leaks.
  • Adds guard/magic techniques to detect overwrites:
    • Over-allocates and inserts sentinel values after allocations.
    • Verifies integrity to catch buffer overruns.
  • Treats debugging as a strategy to make bugs visible:
    • Crashes are useful because they force the fix.

OS/virtual memory and safety boundaries

  • Explains virtual memory and memory page permissions (read/write/execute).
  • Highlights that memory bugs can be caught when code writes outside its authorized region.

realloc as “not necessarily slow”

  • Argues realloc is often better than feared:
    • Memory may not be contiguous at the hardware level.
    • The OS/hardware can remap pages/blocks.
  • Contrasts common advice (“realloc is bad because it copies”) with the speaker’s view that virtualization can reduce practical copying cost.

Cache behavior & performance

  • Portrays memory access as much slower than arithmetic:
    • Registers/L1 cache are fast.
    • Main memory access can be ~50 cycles versus compute that can run far more frequently.
  • Optimization focus:
    • reduce memory accesses
    • improve locality so data stays in cache
    • use arrays instead of pointer-chasing structures

Data structures & performance patterns

Arrays vs linked lists

  • Argues linked lists are often poor for memory coherence:
    • Nodes are scattered in memory → more cache misses.
  • Arrays provide adjacency → better cache utilization.

Dynamic arrays and realloc strategy

  • Recommends growth strategies for resizable arrays:
    • allocate in chunks (e.g., grow by factor/step)
    • keep realloc relatively rare
  • Supports fast removal by swap with end (order not preserved) rather than shifting/moving elements.

“Clarity over cleverness” in code + compiler errors as a feature

  • Rejects the idea that “two lines of code” is inherently good:
    • prefers code that is readable and correct.
  • Claims ambiguity is the enemy:
    • code should be unambiguous to humans and compilers
    • prefer compiler errors over silent guesses
  • Criticizes “clever” features (notably in C++):
    • operator overloading can obscure intent (e.g., whether vector * vector means dot product or elementwise multiply)
    • overload resolution with numeric literals can silently pick the wrong implementation (int/float/double)
  • Reinforces explicitness with patterns like type-suffixed literals (e.g., 4.0f) to avoid unintended overloads.

Tooling & documentation generation

  • Builds a personal website/docs generator by parsing C source:
    • extracts comments
    • associates them with functions
    • generates browsable documentation directly from the codebase
  • Also mentions writing custom tools, including a debugger-like tool.

Naming conventions & coding style (as correctness aids)

  • Prefers descriptive, consistent, long-ish names (“wide code”).
  • Uses naming patterns to reduce confusion:
    • types vs functions vs variables formatted differently so roles are visually distinct
    • strict spacing rules to improve search accuracy (avoids ambiguous matches in text search)
  • Uses semantic naming examples for conventions:
    • count, length, found, next, previous
    • consistent prefixes/suffixes:
      • Funk suffix for function pointers
      • internal for internal-only functions

Function size & structure: “long sequential is readable”

  • Contrasts with “max 5 lines” CS advice.
  • Advocates long sequential functions when the logic is naturally linear (e.g., a render loop):
    • easier to follow state changes over time
    • reduces hidden control flow complexity

API and modular design in C

External interfaces vs internal headers

  • Uses a module style:
    • one external module.h presents the API
    • internal details kept in internal.h (or similarly marked files)
  • Favors “directory-like” naming:
    • function names reflect module ownership (e.g., ImagineLibrary_*, ImagineMutex_*) to locate code quickly.

API design pattern: define the interface first

  • Builds the public API surface first, then fills internal implementations.
  • Makes it easier to swap internal implementations without changing the external interface.

Object-oriented patterns without “C objects”

  • Argues that true OO (“code + data together”) can be misleading in C:
    • code and data are separate (and tied to security: code pages are non-writable).
  • Implements OO-like behavior using:
    • handles returned from *_create() (often as void*)
    • functions that operate on the handle (e.g., *_send(handle, ...))
  • Uses void* as opaque/encapsulated pointers:
    • external code can’t inspect internal representation
    • internal structures can change without breaking callers
  • Shows how a library can define internal structs while exposing only void* handles.

Macros: “useful for rare power, but avoid cleverness”

  • Generally dislikes macros, but approves specific uses:
    • generic code for multiple types (where repetition is unavoidable)
    • debugging instrumentation using file/line
  • Demonstrates macro-based “verbose errors” for binary packing/unpacking:
    • when unpacking fails, errors can mention expected type/name and include file/line
    • targets bugs common in binary protocols where offset/field mismatches cause silent corruption

Memory/pointer fundamentals (conceptual explanations)

  • Explains pointers as addresses into a conceptual “byte street,” where types determine stepping size.
  • Notes that arrays are essentially pointer arithmetic.
  • Discusses struct layout:
    • structs compile to field offsets + total size
    • alignment/padding can increase struct size beyond the sum of fields
    • field ordering can reduce wasted padding (though arrays still follow alignment rules)
  • Mentions “dangerous but possible” low-level packing tricks (manual unaligned reads/writes) with associated risk.

UI/application architecture in C (practical design)

  • Describes a UI widget model where widgets either:
    • draw, or
    • process events/updates
  • Critiques an OO/event system requiring managing many IDs for moving/drawing widgets.
  • Proposed approach:
    • use pointers (unique identities) as widget IDs
    • store widget state internally
    • invoke a widget for different reasons via the same function interface
  • Emphasizes collision/overdraw correctness:
    • UI layering is tracked so input targeting respects which widget is actually visible.

Ending tools/resources and topics mentioned

  • Recommends “classic” algorithms/constants:
    • fast inverse square root approximation (Q_rsqrt style)
    • a simple seeded random number generator
  • Provides resources/locations:
    • website where code/libraries and projects are published
    • open-source code (FreeBSD license mentioned)
    • asks for bug reports and provides contact/social handles

Main speakers / sources

  • Speaker: the single presenter (no named co-speakers). References to their own website/projects and open-source libraries appear throughout.
  • External references mentioned (as topics/links, not quoted sources):
    • Linux/Windows memory tooling (e.g., “G Flags”)
    • C/C++ behavior (C++ operator overloading and overload resolution)
    • OpenGL state behavior
    • fast inverse square root and seeded RNG concepts (described as known techniques)
    • John Carmack, via a tweet about fly-by-wire code reliability

Original video