A browser-based visualizer for C memory models — write C, watch the stack, heap, and pointers come to life.
Built for students learning C systems programming (in the style of the University of Toronto's CSC 209 memory diagrams): type code on the left, and a precise Section / Address / Value / Label memory diagram builds up on the right as you step through execution — one statement at a time.
Exam questions in a systems course often read: "Fill in the memory diagram showing the state exactly before the return on line 8." Reasoning about that state by hand — every address, every stack frame, which pointer points where — is the hard part of learning C.
CMemoryViz makes it interactive. It runs entirely in your browser (there is no server and no real compiler): a hand-written C interpreter executes a teaching subset of the language and models memory the way the course does — explicit hex addresses, separate Read-only / Globals / Heap / Stack regions, labeled stack frames, literal pointer values drawn as arrows, and ??? for uninitialized memory.
Because the interpreter records a snapshot before every statement, you can scrub the whole execution timeline and land on any point — making "the state before line 8" a single click.
Inspired by MemoryViz (which visualizes Python), rebuilt from scratch around C's fundamentally different, address-based memory model.
| 🧠 Real interpretation | Type arbitrary C and watch memory update — not canned animations. Functions, recursion, pointers, arrays, structs, strings, and dynamic memory all work. |
| ⏯️ Step & scrub | Play through execution statement by statement, or drag the timeline to any point. The current line is highlighted in the editor. |
| 🎯 Breakpoints | Click the gutter to set a breakpoint; Run stops at the first hit and Continue jumps to the next — ideal for "state exactly before line N". |
| 🔦 Step-diff highlighting | Cells created or changed by the statement that just ran are tinted, so each step's effect on memory is obvious at a glance. |
| 🧩 Pointer arrows | Every pointer is drawn to the cell it references, with automatic channel routing to minimize crossings. Dangling pointers are flagged. |
| 🧪 Heap report | Live allocation tracking: total malloc/free, bytes live at the current step, and a list of leaks (blocks never freed) with the line each was allocated on. |
| 🍴 Process view | fork() spawns child processes with their own memory; a process tree lets you switch between them and watch parent/child state diverge. |
| 🗂️ File-descriptor table | open / close / dup2 / pipe update a per-process fd table shown alongside the diagram. |
| 📝 Exam mode | Blank out the Value/Label columns and hide the arrows, then fill in the diagram yourself and Check it — lenient grading (hex case, NULL, ???, chars) with per-cell and per-arrow feedback, or Reveal the answer. |
| 📚 Challenge bank | Curated, exam-style problems that open directly in exam mode paused at the target line. |
| 🔗 Shareable links | One click copies a URL that reproduces the exact code, step, and breakpoints — send a classmate "the state before line 8." |
| ⬇️ Export | Save any diagram as SVG or PNG for problem sets and study notes. |
| 🩺 Teaching-grade diagnostics | Use-after-free names the freed block; double free, invalid free, and uninitialized reads report the offending address and line. |
The pipeline is a classic tree-walking interpreter, with one twist: it snapshots memory before every statement so the UI can time-travel through the run.
flowchart LR
A[C source] --> B[Lexer]
B --> C[Parser]
C --> D[AST]
D --> E[Interpreter]
E -- snapshot before<br/>every statement --> F[(Trace)]
F --> G[Memory diagram]
G -.->|scrub / step| F
The memory model uses a deterministic "clean" address allocator so diagrams are reproducible and readable, matching the conventions used in course handouts:
int/ pointer /floatare 4 bytes (4-byte aligned);doubleis 8 bytes (8-byte aligned);charis 1 byte.- Fixed section bases — Read-only
0x104, Globals0x1a0, Heap0x240, Stack0x444— and functions get pseudo-addresses from0x50so function pointers carry real values. - Each function call pushes a labeled frame at a higher address; uninitialized cells render as
???; globals are zero-initialized, as in real C.
Click to expand the full language coverage
- Functions — calls, recursion,
return, and function pointers (int (*fp)(int, int),fp = add,(*fp)(...), function-pointer parameters) - Types —
int,char,void,float,double, pointers, arrays (includingint a[] = {...}and multidimensionalint m[2][2] = {{...},{...}}), andstruct(including{...}initializers) - Global variables — their own diagram section, zero-initialized
- Strings —
char s[] = "hi"(bytes on the stack) andchar *s = "hi"(read-only section), rendered byte-by-byte including'\0' - Dynamic memory —
malloc/calloc/realloc/free,sizeof, casts, andint **2D dynamic arrays - Operators — arithmetic (float-aware), comparison, logical, bitwise, and pointer arithmetic;
&,*,[],.,->, pre/post++/-- - Control flow —
if/else,while,for,break,continue - Processes —
fork,getpid,getppid,wait,waitpid - File descriptors —
open,close,dup,dup2,pipe - I/O —
printfwith%d %i %u %c %s %f %g %p %x %%, captured to an on-screen stdout;strlen
Prerequisites: Node.js 18+ and npm.
# Clone and install
git clone https://github.com/YheChen/CMemoryViz.git
cd CMemoryViz
npm install
# Start the dev server (http://localhost:5173)
npm run dev
# Run the test suite
npm test
# Lint and format
npm run lint
npm run format # or: npm run format:check
# Build for production (type-checks, then bundles to dist/)
npm run buildBecause the interpreter and diagram model are UI-free, the same engine drives a headless CLI that renders a program's memory diagram straight to SVG (or a text table) — handy for generating figures for problem sets and slides.
# Render the state just before the return on line 10 to an SVG file
npm run cli -- examples/scale.c --line 8 --note return -o diagram.svg
# Print a plain-text table to the terminal instead
npm run cli -- examples/scale.c --line 8 --note return --text-l, --line <n> state just before source line <n> -o, --out <file> write SVG here
--note <s> prefer the step with this note -t, --text text table
-s, --step <n> render trace step <n> (0-based) -h, --help usage
src/
├─ interpreter/ # The engine — pure TypeScript, no DOM
│ ├─ lexer.ts # C tokenizer
│ ├─ parser.ts # recursive-descent parser → AST
│ ├─ ast.ts # AST node & C type definitions
│ ├─ memory.ts # sections, address allocator, typed cell store
│ └─ interpreter.ts # evaluator; snapshots memory before each statement
├─ render/
│ └─ svgRenderer.ts # DOM-free SVG/text renderer (shared by the CLI)
├─ cli.ts # headless CLI entry point
├─ components/
│ ├─ CodeEditor.tsx # Monaco editor, breakpoints, error squiggles
│ ├─ MemoryDiagram.tsx # SVG diagram renderer + pointer arrows + export
│ ├─ ExamDiagram.tsx # fill-in-the-blank grading mode
│ ├─ HeapReportPanel.tsx# leak / allocation report
│ ├─ ChallengePicker.tsx# practice-problem menu
│ └─ Controls.tsx # run / step / scrub the trace
├─ challenges.ts # curated exam-style problems
├─ share.ts # URL-hash state encoding for shareable links
└─ App.tsx # composition root
The interpreter has zero UI dependencies — it's plain TypeScript that turns a source string into a trace of memory snapshots, which keeps it fully unit-testable and keeps rendering concerns out of the language semantics.
The interpreter and supporting logic are covered by Vitest — 70+ tests across lexing/parsing/evaluation, the heap lifecycle report, realloc/int ** semantics, share-link round-tripping, diagram diffing, fork() process modelling, and every bundled challenge. The suite includes a test that reproduces a known program's memory diagram address-for-address, and a differential suite that checks the interpreter against a real C compiler.
Differential testing against a real C compiler. Because the interpreter is a pure string → trace function, a corpus of C programs (test/corpus/) is compiled and run with an actual C compiler and the interpreter's stdout is asserted to match — ground-truth validation that runs on every CI build (GitHub's runners have gcc). Locally it skips gracefully if no compiler is installed.
npm testThe app is a fully static Vite build with no backend, deployed on Vercel with automatic production deploys on every push to master. Because npm run build runs tsc first, type errors block bad deploys.
- Watch expressions — evaluate
p->next->vallive at any step - More of the C standard library:
memmove,snprintf,atoi -
exec*and richerpipedata flow between processes
On
fork(): processes are modelled by deterministic re-execution — each process is an independent run of the program that takes a fixed branch at eachfork(). This makes per-process memory and the process tree exact for input-free programs, but does not model concurrent interleaving of output.
Contributions are welcome — see CONTRIBUTING.md for setup and
the check suite. In short: npm run lint, npm run format:check, npx tsc --noEmit, npm test, and npm run build should all pass (CI enforces them).
- MemoryViz for pioneering this style of teaching visualization for Python.
- The University of Toronto CSC 209 course, whose memory-diagram conventions this tool mirrors.
Released under the MIT License.