Memory Spy: A Playground for Peeking at C’s Variable Representations

We’ve released a new playground, Memory Spy, designed to help people understand how programs actually store variables in memory. It’s a companion to our upcoming zine on how integers and floats work, so the focus is on number type representations.

The tool runs C programs and shows you the exact byte-level representation of each variable on every line of code. It’s built for beginners—even those who have never written a line of C—and comes with a set of very simple example programs to experiment with.

Why C?

C is the language where the mapping between source code and memory is most direct. That makes it the ideal teaching language for this concept. But you don’t need to write C to benefit from the playground.

The representation of integers and floats is largely uniform across low-level languages. A 32-bit unsigned integer in C has the same in-memory format as one in C++, Rust, Go, or Swift—only the type names differ. Even in higher-level languages like Python, a float is just a wrapper around a C double, so the C representation remains relevant.

The included examples avoid fancy C features. Most are as simple as char byte = 'a';, so you can follow along even with zero prior C knowledge.

How It Works

Under the hood, the playground’s server:

  • Compiles the submitted C program with clang.
  • Runs it under the lldb debugger, controlled by a Python script.
  • Returns a JSON file that lists each variable’s byte array on every line.

The frontend then formats those raw bytes into a readable view. The display layer is a relatively thin wrapper around the debugger’s output.

Known Limitations

The underlying machinery handles only a subset of C. Current restrictions include:

  • No support for loops—they run, but only the first iteration’s variable values are reported.
  • Only one variable definition is allowed per line.

There are likely other edge cases, as this is intentionally a small, focused project.

Inspiration and Design

Python Tutor by Philip Guo was a significant influence. Its focus is on building a mental model of variables and control flow; Memory Spy narrows that lens to low-level data representation.

The most successful design decision was replacing typical debugger step arrows with clickable code lines. Instead of stepping through a program one line at a time with next/previous buttons, you click any line to see its variable values. This works because the examples avoid loops, which would make a line execute multiple times with different states.

All navigation also uses plain URLs (e.g., <a href="#example=hexadecimal">), with a onhashchange event handler updating the page to match the current hash. This keeps the tool simple and makes every view shareable.

Security and Tech Stack

Running arbitrary code on a server requires caution. The approach here is less about heavy-duty sandboxing and more about making the target not worth attacking. The backend runs in Go, with a Python script driving lldb. The sandbox uses bubblewrap. Processes are killed after one second, run in a container without network access, and execute on a small machine with no sensitive data.

The frontend is built with Vue and relies on tree sitter to detect which code lines contain variable definitions. The source for both the lldb script and the Go server is available as a reference.

You can try Memory Spy at memory-spy.wizardzines.com.